在软件工程漫长的演进史中,如何控制系统复杂度、降低维护成本一直是核心课题。DRY(Don't Repeat Yourself)原则是软件开发中最著名、最基础但也最常被误解的原则之一。它由 Andy Hunt 和 Dave Thomas 在其经典著作《程序员修炼之道:从小工到专家》(The Pragmatic Programmer)中正式提出。DRY 原则不仅仅关乎“不写重复的代码”,更关乎“知识与意图的单一源头”。


1. 核心原则

“系统中的每一项知识都必须在系统内具有唯一、无歧义且权威的表示。”

(“Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”)

通俗地说:DRY 的本意并非指文本意义上的“代码复制粘贴”,而是指“系统逻辑与知识表示的重复”。当相同的知识在多个不同的地方被表达时,如果这个知识发生变更,开发人员必须同步修改所有地方。这不仅会导致遗漏和引入 Bug,还会显著拉高软件的维护边际成本。


2. 经验背后的本质

理解 DRY 原则,需要跨越“代码行完全相同”的狭隘认知,从底层逻辑透视其起作用的本质原因:

① 知识的唯一性与变更一致性(Single Source of Truth)

软件是一个随业务发展而演化的活体,变更(Change)是软件开发唯一的常量。DRY 的终极目标是为了应对变更。如果系统里有一条核心业务规则(例如:“黄金会员的折扣为 85 折”),它被写在了前端校验逻辑、后端结算模块、财务审计脚本以及数据库存储过程中。当折扣调整为 82 折时,你需要找出所有四处并手工修改。一旦漏改一处,系统的数据一致性就会崩溃。DRY 要求这条规则有且仅有一个权威源,其他系统通过 API、公共常量或共享模块去读取这一源头。

② 误区:重复代码 vs. 巧合重复(Accidental Duplication)

初学者往往为了 DRY 而 DRY,强行抽离看起来相似的代码,这会导致“过度设计”和更糟糕的“强耦合”。

  • 本质重复:两段代码表达的是同一个业务知识,如果知识改变,两段代码必须同步改变。这必须 DRY。
  • 巧合重复:两段代码现在长得一样,但它们代表的是完全不同的业务概念。例如,“校验手机号格式”和“校验邀请码格式”可能刚好都是 11 位数字的正则校验。但它们在业务语义上是独立的,一旦邀请码规则改变,手机号校验并不需要跟着变。强行把它们抽离成一个 validateElevenDigits 函数,就是落入了巧合重复的陷阱,导致原本无关的两个业务模块产生了脆弱的强耦合。

③ 降低认知负荷与审计复杂度(Cognitive Load)

当一个工程师在代码库里看到两段几乎相同但略有差别的逻辑时,他会产生极大的疑虑:“这两段代码是有意存在微小差别以应对不同场景,还是因为历史遗留粘贴导致的疏忽?”这种疑虑会极大地消耗工程师的脑力,延缓开发速度。实现 DRY 可以消除这种多余的选择和猜测,提高代码的阅读确定性。


3. 实用案例分析

🚫 反面典型:某电商系统计税逻辑的“多点散落”惨剧

某中型跨境电商平台在发展初期,由于追求开发速度,未对底层知识进行提炼。

灾难场景

平台中关于“欧洲关税与增值税计算逻辑”的代码散落在了三个不同的模块:

  1. 订单结算服务 (Java):用于生成支付账单并进行扣款。
  2. 报表生成服务 (Python):用于向财务部门提供每日交易与扣税明细。
  3. 前端购物车预览 (TypeScript):用于在用户结账前,实时估算并展示税费。

某年,欧盟突然调整了电子服务的增值税率(从 19% 调至 21%)。

  • 订单结算服务的开发人员修改了 Java 代码中的税率常量。
  • 报表生成服务的负责人由于休假,完全不知道税率调整,Python 代码依然使用旧税率。
  • 前端购物车开发人员直接硬编码了 19% 的估算逻辑,导致用户看到的价格与最终扣款金额不一致,引发大量客诉。

结果

在接下来的一整个季度里,财务报表上的实扣税额与申报税额出现了数百万欧元的对账差额。审计机构介入调查,研发团队花了整整一个月时间在混乱的代码库中排查每一处计税公式,严重拖累了新业务的迭代进度。


4. 行动指南

为了在实际开发中完美落地 DRY 原则,架构师和技术主管应践行以下具体建议:

  • 从“语义与知识”出发,而非文本:在重构重复代码前,先问自己:“如果这部分业务规则变了,两边是否必须同时修改?”如果答案是“是”,毫不犹豫重构它;如果答案是“不一定,这只是巧合”,请保持现状,避免过度封装。
  • 建立单一可信源(SSOT):将业务配置、税率、状态机枚举、核心公式抽离到独立的基础服务、共享库或数据库配置表中。禁止在代码中硬编码任何关键业务规则。
  • 警惕跨端知识重复:在前后端分离架构中,表单校验逻辑经常出现双重硬编码。可以尝试使用协议驱动开发(如 Protobuf、JSON Schema 或 OpenAPI Spec),通过一份源定义文件,自动生成前端 TypeScript 校验代码与后端 API 验证器,从而达到高级别的 DRY。
  • 利用“正交性”实现逻辑复用:如果必须要提取重复代码,应当保证抽离出来的工具函数具有极高的正交性(即无副作用、职责单一、不依赖特定的业务上下文),这能保证复用时不会引入意外的连带影响。