9 DRY——邪恶的重复
区分代码相似与知识重复,让每条业务知识拥有清晰源头并能检测投影漂移。
学习目标
- 能把“9 DRY——邪恶的重复”区分为代码相似、知识重复和投影,并为一条业务规则找到可复核的权威源
- 能用“提示15:DRY——不要重复自己”和“提示16:让复用变得更容易”设计引用、生成、复用和漂移检测边界
- 能在真实项目中故意改变一份投影,定位首个漂移,恢复单一源并让独立复核者重放正常、边界和一次故障样本
为什么 9 DRY——邪恶的重复不是复制粘贴清理
本页依据 David Thomas、Andrew Hunt《程序员修炼之道:通向务实的最高境界(第2版)》,云风译,电子工业出版社,2020年4月,ISBN 9787121384356 的公开完整中文目录,独立重构 9 DRY——邪恶的重复。本页不复制原书正文、插图、练习答案或代码,而把目录命题转写成单一源、投影回路、漂移反例和复核证据。
DRY 关注的是知识重复,而不是所有看起来相似的字符。两个函数可能因为输入形状相同而相似,却表达不同业务规则;一条税率、权限或接口约束也可能同时出现在代码、配置、测试、文档和运营表格中,表面上不相似,实际却在重复维护同一知识。
本单元把问题拆成 知识识别 → 权威源 → 投影 → 漂移检测 → 修复。每一步要写清源头、消费者、更新触发器、校验方式、所有者和拒绝条件;复用的价值是减少知识分叉,不是让所有调用者共享一团隐含状态。
9 DRY——邪恶的重复:单一源合同
| 节点 | 要问的问题 | 可观察产物 | 失败动作 |
|---|---|---|---|
| 知识识别 | 哪条事实或规则被多个位置维护 | 规则名称、边界和消费者 | 区分相似实现与同一知识 |
| 权威源 | 哪个位置拥有修改责任 | 源文件、owner 和版本 | 合并或明确不同规则 |
| 投影 | 其他位置怎样获得该知识 | 引用、生成物、适配器或链接 | 移除独立副本 |
| 漂移检测 | 源头改变后谁会发现不一致 | 测试、构建检查和差异报告 | 阻止过期投影继续发布 |
| 修复 | 发现漂移后怎样回到一致状态 | 修复记录、重放和回归 | 先恢复源头,再修消费者 |
Knowledge Duplication 是需要控制的对象;代码长得像并不自动证明知识重复。
↡对某条知识拥有修改责任、版本和接受边界的唯一权威位置。Canonical Source 让团队知道哪里可以改、谁需要评审,以及其他位置如何获得更新。
↡从 Canonical Source 通过引用、生成、适配或发布得到的代码、文档、配置或测试表示。Projection 可以有多个,但不能获得独立修改同一规则的权力;每个投影都应保留来源关系。
↡源头与投影对同一知识的表达不一致,或投影在源头变化后仍使用旧事实的状态。Drift 不只发生在代码中,文档、配置、数据迁移和运营脚本同样可能漂移。
↡在源头变化或构建发布时比较源头与投影、并在不一致时阻止或告警的自动或人工检查。Drift Check 的目标是尽早暴露分叉,而不是用一个“同步成功”标记掩盖未检查的消费者。
提示15:DRY——不要重复自己
提示15:DRY——不要重复自己 的关键词是“自己”:同一条知识不应由多个位置独立决定。先问两个位置是否会因为同一业务事实一起变化、是否应该接受同一边界和错误语义;如果答案是肯定的,就应建立 Canonical Source 和 Projection 关系。
例如订单税率同时存在于结算代码、发票模板和运营表格中。它们的格式不同,但都表达同一条规则;正确的修复不是把三个文件机械合并,而是选定规则源,让模板和配置引用或生成于该源,并用 Drift Check 证明更新不会只触达其中一处。
提示16:让复用变得更容易
提示16:让复用变得更容易 不是鼓励把所有行为塞进一个万能组件。复用入口要说明输入、输出、生命周期、错误、所有权和可替代边界;如果调用者必须记住一串隐含状态,复制反而比复用更容易,系统就会产生新的知识分叉。
让复用变容易的证据包括:稳定的内部协议、最小示例、明确的失败路径、可发现的接口、契约测试和来源链接。复用者不必了解源头内部实现,但应能知道何时不适用,以及规则更新会通过什么检查到达自己。
从提示到可失败的因果链
本页主链是 知识识别 → Canonical Source → Projection → Drift Check → 修复。每条边要写明传递的是规则、数据、格式、责任还是反馈;每个节点都要有输入、输出、owner、接受条件和回退动作。只有画出多个箭头而没有来源关系,不能证明 DRY。
先写预测,再执行正常样本、边界样本和一次故障样本。三类样本共享项目版本和输入,只改变一个投影或检查条件;如果不同位置给出不同结果,记录首个漂移和恢复动作。不要同时重命名、换生成器和改变业务规则,否则无法归因。
单一源不是单一文件
一个 Canonical Source 可以是领域模型、配置 schema、决策记录、迁移脚本或经过评审的规则表,关键是它有明确 owner、版本和接受边界。Projection 可以是生成代码、文档页面、测试数据、API 示例或部署配置;它们应标明来自哪里、何时生成、怎样验证。
如果一个投影需要手工修改,先判断它是否包含真正独立的知识。如果包含,就拆成两个明确的规则并分别命名;如果不包含,就让源头拥有它,或者把手工步骤改为可检查的生成或引用。盲目抽象会把不同规则绑在一起,盲目复制则会让同一规则漂移。
漂移检测与修复回路
源头更新后,Drift Check 应在开发、构建或发布阶段尽早运行。失败信息要指出源头、投影、差异、责任人和修复入口;只说“同步失败”无法帮助复核者重建首个分叉。修复时先确认 Canonical Source 的状态,再重新生成或更新投影,最后执行消费者回归和用户边界检查。
三步完成一次 DRY 重构
先识别知识而不是字符
选择一条跨代码、配置、测试或文档的业务规则,写出它的边界、消费者、变化频率和错误代价。比较两个位置是否应该同时接受同一变更,建立 Knowledge Duplication 清单。
可重放的 DRY 记录
dry_record:
unit: tpp20-topic-09-dry-duplication
knowledge: 订单税率与生效日期
canonical_source: pricing/tax-policy.yaml
projections: 结算服务、发票模板、运营查询页
reuse_boundary: TaxPolicyReader 只返回已校验的领域对象
drift_check: schema 校验、生成差异、契约测试
injected_change: 只修改发票模板中的税率
first_mismatch: 模板金额与结算金额不一致
recovery: 恢复模板生成关系,重建投影并重跑用户样例这份记录把“减少重复”变成可以复核的关系。若一个生成物看似最新,却没有来源链接和生成版本,它仍是待验证的副本;若复用接口减少了代码,却把错误处理和生命周期藏在调用者记忆里,提示16也没有真正达成。
选择、拒绝与边界矩阵
| 判断 | 接受证据 | 应拒绝的信号 |
|---|---|---|
| 同一知识 | 位置会随同一规则、边界和错误语义一起变化 | 只有字符串相似,没有共同规则 |
| 单一源 | owner、版本、范围和来源关系明确 | 多个副本都能被直接编辑 |
| 投影 | 生成、引用或适配方式可重放 | 更新靠聊天提醒或人工复制 |
| 复用 | 输入、输出、失败和生命周期清楚 | 调用者依赖隐含顺序和全局状态 |
| 漂移检测 | 构建或发布能指出源头与投影差异 | 只检查“文件存在”或最终截图 |
| 修复 | 恢复来源关系并通过消费者回归 | 只改当前显示错误的副本 |
迁移到云服务、数据系统或 AI 辅助开发时,要分别记录生成器版本、数据敏感性、发布频率、自动化反馈和人工责任。自动生成减少了机械重复,却不能替团队判断两个位置是否表达同一知识。
常见误区
本章回顾
掌握 9 DRY——邪恶的重复,不是看到重复字符就抽象,而是能识别同一知识,指定 Canonical Source,让 Projection 通过清晰边界复用,并用 Drift Check 及时发现分叉。按 提示15:DRY——不要重复自己 管理知识源,按 提示16:让复用变得更容易 设计可发现、可失败的入口;修复结论必须包含源头、投影、首个漂移和回归证据。
可验证练习
练习
本组练习覆盖 9 DRY——邪恶的重复、提示15:DRY——不要重复自己 和 提示16:让复用变得更容易,要求提交 Knowledge Duplication、Canonical Source、Projection、Drift Check 和复用边界证据。
问题 1: 两个函数长得很像,但一个处理退款,一个处理支付。怎样判断是否应该共用实现?
问题 2: 如何实践“提示15:DRY——不要重复自己”,让税率同时进入结算、发票和运营页面?
问题 3: 如何实践“提示16:让复用变得更容易”,而不制造一个依赖隐含状态的万能组件?
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- Knowledge Duplication
在多个位置独立维护、却表达同一事实、规则或约束的知识重复。
- Canonical Source
对某条知识拥有唯一修改责任、版本和接受边界的权威位置。
- Projection
从权威源通过引用、生成、适配或发布得到的代码、文档、配置或测试表示。
- Drift
源头与投影对同一知识表达不一致,或投影仍使用旧事实的状态。
- Drift Check
在源头变化或构建发布时比较源头与投影并阻止或告警的检查。
前后导航
来源与改写范围
- Pragmatic Programmer 作者页面:核对 Topic 9、提示15和提示16的版本位置与主题范围。
- 中文目录页面:核对 9 DRY——邪恶的重复、提示15:DRY——不要重复自己、提示16:让复用变得更容易的公开目录范围。
- 出版社书目信息:交叉核对中文译本的出版信息与版次边界。