代码重复 vs. 错误抽象:平衡 DRY 与过度工程化
错误抽象的代价
维护重复的代码通常比管理一个错误的抽象要便宜得多。虽然 DRY (Don't Repeat Yourself) 原则被广泛传授,但过早或教条式地应用它可能会导致“过度工程化”的代码库,这些代码库比存在简单重复的代码库更难修改。
一个设计不足的代码库通常比一个被复杂、僵化的抽象所累赘的代码库更容易处理,因为这些抽象并不能准确反映问题领域。当抽象是错误的时候,开发者往往发现自己在与自己创建的框架作斗争,导致系统变得脆弱,任何单一的更改都需要在继承类或参数化函数的迷宫中穿行。
区分偶然重复与真实重复
并非所有的代码重复都是一样的。是否决定进行抽象取决于重复是“真实的”还是“偶然的”。
- 真实重复: 当相同的逻辑应用于多个地方,因为它代表了一个单一的、普遍的真理(例如,一个特定的物理公式)时,就会发生这种情况。这应该被抽象化,以确保“单一真理来源”。
- 偶然重复: 当两个不同的功能在今天看起来一样,但会独立演进时,就会发生这种情况。强行将它们放入一个单一的抽象中会产生“长距离耦合”,即一个功能的更改会意外地破坏另一个功能。
正如一位贡献者所指出的,如果重复代码可以防止在将两个分歧路径强行合并为一个时可能发生的 bug,那么重构就是必要的。然而,如果抽象仅仅是为了方便,并且开始变得不方便,那么它就已经失去了它的目的。
确定何时进行抽象的策略
在重复与抽象之间寻找平衡是核心工程技能。有几种启发式方法用于确定重构的最佳时机:
三次法则
许多开发者遵循“三次法则”,该法则建议重复两次是可以接受的,但当相同的模式第三次出现时,就是进行抽象的时候了。这可以防止基于单个相似实例的过早抽象。
压缩 vs. 抽象
区分减少代码行数(压缩)和提取概念上连贯的部分(抽象)是有帮助的。高质量的工程实践优先考虑概念抽象,而非简单的行数压缩。
接口优于继承
为了避免深层继承树的陷阱——这是过度遵循 DRY 的常见结果——许多工程师更倾向于使用接口。这允许正交的代码保持灵活,并且比僵化的类层次结构更容易维护。
重复的反对观点与风险
虽然“重复更便宜”的论点很受欢迎,但它也带有风险,特别是在大规模应用时:
- 大规模下的维护负担: 对于某些人来说,一旦项目达到一定规模(例如,数十个客户),重复就会变得极其昂贵。在许多实例中维护重复的代码可能会耗尽开发者的资源。
- LLM 因素: 在 AI 生成代码的时代,重复可能是危险的。可能无法信任 LLM 在每个重复模式中一致地应用相同的修改,从而可能通过不一致性引入 bug。
- ** sunk cost 与惯性:** 有些人认为,广泛的重复会导致维护成本过高,以至于没有人觉得有权进行重构,从而创造出一种与错误抽象不同类型的技术债务。
工程权衡总结
| 方法 | 主要收益 | 主要风险 |
|---|---|---|
| 接受重复 | 避免过早、僵化的抽象;更容易快速迭代。 | 分歧风险;重复更改的高手动工作量。 |
| 应用抽象 | 单一真理来源;减少行数;更容易进行全局更新。 | “错误”抽象的风险;产生复杂的耦合;更难撤销。 |