复制粘贴(copy-paste)——是一种将代码片段从一个地方复制到另一个地方而不适应新上下文的做法。通常情况下,开发人员会从现有模块复制一个块,进行最小的修改,然后粘贴到新的模块中——连同错误、过时的注释和不必要的依赖一起。根据TIOBE Code Quality Survey (2025)的研究,复制粘贴程度高的项目每千行代码中的缺陷数量是单一抽象项目的三倍。代码重复——技术债务的主要来源:每个副本都需要单独维护,在一个地方修复错误并不能保证在其他地方也得到修复。
要点
复制粘贴(copy-paste programming)——是将现有代码转移到新位置,进行微小修改或不修改的做法。该术语带有贬义:它暗示开发人员不是在设计解决方案,而是机械地复制现成的块,通常并不完全理解其工作原理。
复制粘贴有两种类型:合理的(intentional)和偶然的(accidental)。合理的——当开发人员有意复制代码并计划后续重构时(但计划常常未能执行)。偶然的——当重复代码在不知不觉中产生时,例如两个开发人员独立为不同屏幕编写相同的逻辑。
根据SonarQube State of Clean Code (2025)报告,重复代码平均占商业项目总代码量的12-18%。同时,修复重复代码中错误的成本比单一实现的代码高出2.5倍,因为开发人员必须找到并修复所有副本。
对抗复制粘贴的主要工具是DRY(Don't Repeat Yourself)原则。然而,将DRY绝对化也是危险的:有时当两个副本必须彼此独立演化时,复制是合理的。重要的是区分“偶然重复”(需要消除)和“必要重复”(需要文档化)。
第一个也是最重要的危险——错误的繁殖。如果源代码中存在缺陷,它会随着代码一起被复制到所有新位置。当缺陷在源模块中被发现并修复后,副本仍保持未修复状态。开发人员甚至可能不知道错误存在于五个不同的文件中。
第二个危险——不平衡的演化。同一算法的两个副本随着时间的推移会积累不同的修改。在一个副本中添加了边界值验证,在另一个副本中——改变了输出格式。几个月后,无法确定哪个版本是“正确的”,项目失去了行为的一致性。
第三个危险——测试量的增加。每次复制粘贴都需要自己的测试。如果公共逻辑被提取到一个函数中,可以用一套测试覆盖并重复使用。而重复时,每个副本都必须单独测试——这会成倍增加CI运行时间和维护的测试库规模。
第四个危险——效率的错觉。复制粘贴造成了一种虚假的速度感:开发人员快速插入代码并看到屏幕运行正常。但这种“速度”会变成了技术债务,当在重复块中发现错误或需要更改业务逻辑时,必须连本带利地偿还。
理解复制粘贴的原因有助于建立正确的预防措施。大多数情况下,开发人员复制代码不是因为懒惰,而是因为截止日期压力、知识不足或不方便的架构。
第一个原因——截止日期。当需要在两天内完成一个屏幕,而类似的屏幕已经存在时,开发人员会完全复制它,只改动用户可见的部分。没有时间通过提取公共组件来进行重构——客户期望结果。结果就出现了第二个屏幕,包含80%的公共代码,但有独立的修改历史。
第二个原因——缺乏统一抽象。如果项目中没有针对典型任务的公共组件(例如带下拉刷新的列表屏幕),每个开发人员都会编写自己的实现或复制相邻的实现。项目初期做出的架构决策直接影响未来复制粘贴的数量。
第三个原因——害怕破坏工作代码。开发人员知道现有模块能正常工作。通过提取公共代码进行重构可能会影响到现有功能。如果测试覆盖率低,破坏的风险超过了重构的感知收益,开发人员就会选择安全路径——复制。
消除原因,而非症状。缩短截止日期和引入代码审查并不能解决问题,如果项目中缺乏共同的架构基础。在早期阶段投入时间创建可复用组件——这是减少未来复制粘贴诱惑的唯一方法。
复制粘贴的搜索由自动分析器完成,它们比较代码片段并确定超过设定阈值的匹配。最好的工具在AST(抽象语法树)级别工作,忽略格式、变量名和注释。
PMD CPD(Copy-Paste Detector)——是Java、Kotlin、Swift、JavaScript、Python和C++中最常用的工具。CPD分析源代码的令牌,并找到超过设定最小令牌数(默认为100)的重复。阈值的设置是获得高质量结果的关键:过低的阈值会产生大量误报(如import等常见模式),过高的阈值则会漏掉真正的重复。
plugins {
id 'pmd'
}
pmd {
toolVersion = '7.0.0'
ruleSetFiles = files("pmd-rules.xml")
}
tasks.register('cpd') {
doLast {
exec {
workingDir = projectDir
commandLine 'cpd',
'--minimum-tokens', '75',
'--language', 'kotlin',
'--files', 'src/main/kotlin',
'--format', 'xml',
'--failOnViolation', 'true'
}
}
}
SonarQube将重复检测器直接嵌入到Quality Gate中。Duplicated Blocks (%)规则显示重复代码的比例。5%的阈值对于商业项目来说是健康的。超出阈值会阻止向发布分支的推进。SonarQube还按类型对重复进行分组:精确复制(exact match)和结构复制(具有修改后的名称)。
对于JavaScript和TypeScript,重复代码由ESLint配合eslint-plugin-sonarjs插件(no-duplicate-string规则)和支持150+语言的jscpd工具进行搜索。jscpd特别适用于单仓库:它可以在包之间找到重复,而不只是在单个模块内部。
复制粘贴的重构归结为一个原则:提取公共部分并将差异参数化。具体技术取决于重复的范围和上下文。
最简单的案例——单个类内的重复(例如,两个方法具有相同的逻辑但不同的类型)。解决方案——通过泛型进行泛化或使用类型参数重用方法。如果重复涉及多个类——将公共代码提取到工具类或扩展函数中。
更复杂的案例——屏幕或模块级别的重复。这里简单的函数提取无济于事,因为UI结构、生命周期逻辑和数据绑定都重复了。解决方案——创建一个公共的屏幕基类或复合视图组件,通过参数或协议传递差异。
// 之前 - 同一个UITableViewController的两个副本
class UserListController: UITableViewController {
private let viewModel = UserListViewModel()
// 40行代码
}
class ProductListController: UITableViewController {
private let viewModel = ProductListViewModel()
// 相同的40行但使用Product代替User
}
// 之后 - 共享通用基类
class ListViewController<T: ListViewModel>: UITableViewController {
let viewModel: T
// 40行代码 - 仅一次
init(viewModel: T) {
self.viewModel = viewModel
super.init(style: .plain)
}
}
最复杂的案例——微服务或库之间的重复。提取公共代码可能导致循环依赖或不必要的耦合。在这种情况下,复制粘贴可能是一个有意识的决定:两个团队维护独立的服务,公共库带来的问题比解决的更多。最重要的是——记录这样的决定,并定期检查副本是否已经分歧到需要统一的时候了。
预防复制粘贴比重构已经重复的代码更有效。主要预防措施在于开发过程的组织,而非技术。
第一个措施——强调重复的代码审查。审查清单应包括这一点:“这个PR中是否有项目中已存在的代码?”。如果审查者看到复制粘贴——他将阻止合并,直到公共组件被提取出来。这一要求应写入团队的Definition of Done中。
第二个措施——公共组件库。每个出现在两个或更多屏幕上的UI模式都应该被提取到公共模块中。在项目中创建一个共享模块,使其成为所有UI组件的强制入口点。如果组件不存在——先创建它,然后再在屏幕上使用。
第三个措施——CI/CD自动化。在流水线中添加检查重复代码的步骤(PMD CPD、jscpd、SonarQube)。超过阈值——构建错误。开发人员不能合并将复制粘贴比例提高到允许水平以上的PR。这可以将责任从代码审查转移到自动化,并确保没有任何重复被遗漏。
推行“一个实现——一个地方”的文化。如果您看到重用可能性——不要将重构推迟到以后。每个遗留“以后再做”的复制粘贴都会成倍增加并变成无法控制的技术债务。
常见问题
不,存在有意识重复的场景:必须独立演化的不同微服务;为实验复制的代码并带有删除计划;针对不同API版本的模板DTO。关键是记录原因并为重构设定检查期限。
复制粘贴——当代码的两个部分做同样的事情但没有公共抽象时。健康的复用——当公共代码被提取到函数、类或模块中,并且差异被参数化时。如果逻辑更改需要在三个或更多地方进行修改——那就是复制粘贴。
PMD CPD支持Swift和Objective-C。对于Xcode,有SwiftCop等插件和AppCode内置的重复检测器。SonarQube也分析Swift项目,直接在pull request中显示重复块。
为每个大型副本的重构创建技术任务。设定优先级:经常变化的屏幕——优先,稳定的屏幕——其次。对于每个涉及重复代码的新PR,分配15-20%的时间用于逐步整合。
是的,现代AI助手(GitHub Copilot、Codeium)可以分析上下文,并在检测到重复模式时建议提取公共代码。然而,它们不能取代自动分析器——使用Copilot进行预防,使用CPD / SonarQube进行检测。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。