第 16 章:修改既有代码
系列:软件设计的哲学 · 第 16/22 章
全书导读:00-overview.md
一句话总结
改旧代码时默认目标不是「最小 diff 过关」,而是每次变更后复杂度不升反降——战术式补丁是复杂度复利的主要来源。
核心观点浓缩
- 零复杂度增量原则:新功能应尽量落入现有抽象;塞不进则先重构再扩展
- 警惕「就加一行 if」:分支堆积是变更放大与认知负荷的前兆
- 读代码时先重建原作者的设计意图(注释、提交、模块边界),再动刀
- 小步提交:重构与行为变更分开 PR,降低回归风险
- 若模块已浅且脆,别继续堆功能——设计两次(第 11 章)考虑替换抽象
- 童子军规则:每次进入文件,离开时复杂度略降——rename、删 dead code、补一句注释都算
- 合并冲突时优先保留更简单的那一侧抽象,而非「两边逻辑都保留」
关键概念 / 金句
| 战术式改法 | 战略式改法 |
|---|---|
| 复制粘贴类似逻辑 | 提取共用深模块 |
| 暴露内部字段给调用方 | 加窄接口方法 |
| 注释写「临时 hack」永留 | hack 带 issue 与删除期限 |
「大多数复杂度不是一次设计失误,而是千百次『先这样』累积的。」
本章在全书中的位置
第 16 章把全书原则落到维护期——软件寿命主要在改而非写。连接可读性篇(12–18)与后半取舍篇(19–21):会读、会改,才谈得上在潮流与性能间做判断。Ousterhout 指出:新人常以为设计发生在「第一版」,而老手知道第二版起的设计质量才决定系统寿命。
个人思考与启发
订单导航 widget 每加一种状态就多一个 enum 分支,终至 700 行。暂停功能开发,抽出 NavigationState 小状态机 + 表驱动,新状态只增一行配置。多花两天,后续每个需求从改五处变改一处。对 legacy 模块,「读注释重建意图 → 小步重构 → 再叠功能」比边读边加 if 更符合零增量原则。每次重构 PR 附「复杂度前后对比」一两句,便于团队看见收益。维护期设计投资应计入 sprint 容量,而非「有空再说」。
与《程序员修炼之道》对照
- 软件熵:与本章零增量原则同向——主动重构对抗熵增
- 「破窗理论」:容忍 hack 会诱导更多 hack;战略式要求及时还债
- 石头与软泥:核心模块用战略式改,实验性功能可战术式但须隔离
重点与注意
重点:改前问「能否让调用方更简单?」——能则优先改接口形状。
重点:重构预算应常态化(如每迭代 10–20%),非等「大版本再还」。
注意:勿在无关重构 PR 夹带行为变更——审阅者无法同时验证两件事。
注意:Legacy 无测试时,先加** characterization test** 再重构,非裸改。
注意:大功能分支长期存在时,定期与 main 对齐重构,防合并时复杂度爆炸。
注意:「只改一行」的 PR 若触及核心状态机,仍应走设计两次 mini 流程。
导航:第 15 章 先写注释 · 下一篇:第 17 章 一致性