跳到主要内容

老化与保养 — 腐坏的征兆与还债

这一章讲五件事: 怎么「闻」出代码的坏味;技术负债的利息怎么滚; 一扇破窗怎么传染整栋楼;熵(物理学里「混乱程度」的量度)为什么只增不减、七个征兆怎么自查; 以及被反复验证有效的日常对策——童子军规则。 主走查:一笔技术负债从借入到还清(和还不清)的两条时间线。

1. 反直觉的起点:放着不动,代码也会变坏

「代码有熵」要先从物理借一个概念讲清。是物理学里表示「体系混乱程度」的量; 热力学第二定律说,封闭系统的熵只会增加——没有外力,东西自发地走向混乱: 房间不收拾会乱,生肉放着会坏1

代码正是这样。原书的原话:

软件开发可以超越大部分的物理法则,却逃不出熵增原理的束缚。 如果不对代码进行管理,其混乱程度就会不断加深,直到突破极限。2

关键在「不对代码进行管理」这个前提。代码变乱的主渠道不是 bug,而是修改本身: 每次修改都是一次新的决策,决策之间难免互相矛盾;上一章讲的效应局部化做得不到位, 每次修改的痕迹就散落一处;痕迹越积越多,「臃肿的代码越积越多,使得维护难度不断增大」, 最后连很小的修改都要耗费大量劳力,逼着人重新设计3

还要补一层「移动的靶子」:就算你有了明确的重写目标,环境也在变—— 书里的说法是,重新设计很难一帆风顺,因为「我们在实际工作时打的是移动的靶子」4

原书把「改起来要轻松」本身也立成一条架构性质——易变性(软件能被轻易修改、扩展、重组、移植的能力)5。 这条性质的扩展段给了老化一个正式的说法:软件老化——软件像人一样随时间流逝慢慢退化, 原因有四:设计灵活性不足,修改破坏了架构;改的人没理解设计,架构被改坏; 架构本身难懂,被混乱的修改破坏;更新停滞,被时代抛弃6。 本章剩下的篇幅——坏味、负债、破窗、征兆、童子军规则——就是给这四条原因配的对策。

2. 坏味:腐坏的嗅觉系统

这一节回答:腐坏看不见,但闻得到——五个可操作的信号。

「坏味」是本书引用的另一个经典概念(出自福勒《重构》):代码里难以理解、难以修改、 难以扩展的部分。重构(第 01 章引入:不改对外行为,只整理内部结构)是药, 坏味是「该吃药了」的症状7。作者强调:光背重构目录没用—— 目录上的情形和现场对不上,必须能自己嗅出味道8

书里列的五个坏味,每个都带识别特征和处置9:

坏味什么状态为什么是坏事处置
代码重复相同代码散布各处故障时要同时改多处收拢成一个函数(第 03 章)
函数太长翻几页看不到尽头无法用一句话概括它在干嘛拆成小函数(第 02 章 SLAP)
模块太大规模大到难管理责任过重,被改的可能性高拆成多个小模块
模块太多数量多到难管理关联暴增,流程难掌握删中介、合并
名称不一致名字与实际行为对不上名字是界面(第 02 章),界面说谎最贵立刻改名

五个坏味两两配对很有意思:「太大」和「太多」是一对——大模块要拆, 但不能拆过头,拆出成倍的模块,关联反而失控10。分寸感书里没有给公式, 给了方向:拆分的目的永远是「减轻责任」,责任减到位就停。

「名称不一致」值得多看一眼,因为它揭示了一个时间层面的真相: 代码是活的,名字会过期。当初正确的名字,随着修改推进慢慢对不上现实; 发现名字与概念不符,「一定要立刻更正」11——这是第 02 章命名原则的维护态。

3. 技术负债:欠款与利息

这一节回答:有时明知代码不整洁也得先上——这笔账怎么记才不会崩。

技术负债是全章最重要的经济学比喻。场景:两条路,花大把时间写整洁代码, 或者快速写出不整洁的代码;时间紧、故障急的时候,有时只能走第二条。 走了第二条,软件就背上了「债」——债务=代码里难以修改、难以理解的问题代码 (不是故障本身,是「给故障创造条件、阻碍排查」的那部分)12

主走查:一笔负债的两条时间线

负债的可怕在利息。作者说利息就是「不整洁的代码在软件里逗留的时间过久会使问题严重化」—— 读它费时、每次修改都让它更乱、整个软件越来越不稳定13。 拿一个具体场景把两条时间线走完(时间数字是演示编的,量级关系是书里的机制):

时间线甲:借了不还(书里描述的恶性循环)
第 0 天 线上故障紧急,绕过校验逻辑直接改数据流,2 天上线
第 30 天 加新功能:先读懂那笔绕过的逻辑,1 天;改动又绕过一处 → 利息
第 90 天 再加功能:那片代码已没一个人全懂,2 天,继续绕 → 复利
第 180 天 任何新功能都要先排一遍雷;排障时间超过开发时间
——「既不能添加功能,又不能维护」,软件失去提供价值的能力[^12]

时间线乙:借了就还(书里的信用卡模式)
第 0 天 同样的紧急故障,同样 2 天上线欠债
第 3 天 回滚那段应急代码,用整洁写法重做,1 天;
下次发布带上 → 「在还款日之前付清,就不必支付利息」[^13]
第 90 天 新功能直接在整洁版本上做,无历史包袱

图说:两条线第 0 天完全一样;差别只在「还」这个动作有没有发生。

时间线甲的机制,书里叫恶性循环:代码难懂 → 加功能与修故障都要更多时间 → 时间被吞掉、债越滚越大 → 能做的事更少14

负债要显式管理,不许避讳

作者的立场是承认债务、与它和睦相处:在「尽快修复故障」和「蒙受重大损害」之间, 快速写下不整洁代码是正确的选择——错的不在借,在不还、不记15。 没时间还怎么办?底线动作:至少把本应编写的正确设计记录在文档里16

这套比喻还有一层妙用,作者点明:负债是讲给非程序员听的。 对不写代码的上司,「代码能跑就行」很难反驳; 换成「我们欠了一笔债,利息是每次改动的额外工期」,对话就能成立17

配套的概念是「技术病」:问题代码像病,拖着会恶化,该做手术(重构)就得做, 「改善操作要慢且精,每执行一次改善操作就测试一次」——一次性大改,改坏了前面全白干18。 以及「临时解决方案」的惯性法则:临时方案一旦上线就变成既成事实, 没人再觉得需要改它——「本来是临时抱佛脚的产物,但它却长期留存了下来」19。 所以临时方案要和技术负债用同一套账本管理:记账、记还款日。

4. 破窗效应:腐坏是会传染的

这一节回答:为什么第一块脏代码出现后,恶化速度会突然加快。

破窗效应来自城市研究:建筑上一扇破窗长期不修,给人一种「被遗弃」的信号, 于是更多窗户碎掉、垃圾堆满、涂鸦上墙——一扇窗拖垮一栋楼20。 代码同样:不好看的代码放着不管,「不论它多么微不足道,也能在很短的时间内让整个软件腐烂」21

机制不在窗,在人心。书里引了一个心理学实验(信箱实验): 信箱附近有涂鸦和垃圾时,信件被盗率高达 25%——仅仅一点垃圾,就能把许多正直的人变成小偷22。 翻译到代码:看到破窗,程序员脑中冒出的是 「剩下的代码肯定也是一团糟,随便改一改算了」23。 再深一层,书里把原因归结为**「不安」**:长期摆着没人管的小问题, 让人对这里失去维护的预期——「人们对时间长强度小的精神压力更加敏感」24

对策是时间敏感的:发现破窗立即修25。修不了怎么办?书里给了一个细但关键的动作: 至少标出「这段代码不好」——比如用带标签的注释让它出现在 IDE(集成开发环境, 写代码用的软件)的任务列表里。目的只有一个:让烂被记录、被管理, 向所有人宣告「这里不是没人管,是排队等修」——破窗的心理信号就此切断26

5. 七个征兆:熵增的自查表

这一节把「变乱」翻译成七条可以逐条核对的症状。

熵增原理一节(出自《程序员修炼之道》)给了七条代码腐坏的征兆27,每条都值得当自查项:

  1. 刻板:一处修改,牵动所有依赖模块——效应局部化失效的直接读数;
  2. 脆弱:一改这里,别处坏;「越修复质量越差的模块」是它的现形记28;
  3. 可移植性差:想把它挪个环境,处处是粘连;
  4. 难以掌控:做错容易做对难——连「编译太慢」都会逼人抄近道,环境恶化也是腐坏燃料;
  5. 复杂:为想象中的未来预埋的结构——「很遗憾,这样做只会带来相反的效果」29;
  6. 重复:第 03 章的主角;看似相同实有细微差别=没做抽象化的铁证;
  7. 不透明:难以理解,且越改越难懂。

对照第 03、04 章会发现:七征兆没有一条是新病——重复、复杂、刻板, 全是前面原则失效后的「临床表现」。这份清单的价值不在新知识, 在于它把前面所有原则的失效形态集中给了你:当系统开始呈现这些症状, 回去查的一定是某条原则在哪次修改中缺席了。

6. 童子军规则:被验证有效的日常对策

这一节回答:对抗熵,个人每天具体该做什么。

美国童子军有条规则:「离开营地时,营地必须比来时更干净」——哪怕脏不是你弄的。 移植到代码:离开之前,让代码比你接手时更整洁,「代码最初是谁写的并不重要」30

它的力量在复利方向与熵相反。熵的账本上,每次修改加一点混乱; 童子军规则把每次修改变成「顺手减一点混乱」——调整变量名、拆大函数、去一处重复, 「不管改善了什么,改善了多少,都没有关系」,关键是量变引起质变31。 所有人的每次提交都净减一点乱,系统的熵曲线就被掰弯了。

作者还用它反推了「抄近道」的后果清单,三条都值得记住32: 省掉单元测试(针对单个部件的小段自动检查)→ 以后每次修改都要手动全测,软件脆弱; 强行使用不合目的的现有系统 → 早晚返工,成本反增; 发现库不合适却将就 → 只能层层包裹遮丑,「软件不可能进一步得到优化」。 结论一句话:凡是与代码有关的抄近道,维护阶段都会连本带利地还回去

7. 作者的判断与证据

书里给了证据的:

  • 信箱实验(25% 盗率)是心理学界真实发表过的著名实验,书内给出数字与出处方向;
  • 「坏味/重构」出自福勒《重构》,「负债/破窗/熵」出自《程序员修炼之道》,概念链有明确源头。

作者的推测与展开,要分开看的:

  • 利息滚动的具体速度没有量化——本书只给机制,不给曲线;我们主走查里的天数是演示;
  • 「人对长时间低强度压力更敏感」引心理学论断,在代码场景的适用性是作者的类比延伸;
  • 「信用卡模式」的可行性依赖团队允许「上线后再发一版修正」,某些发布节奏下做不到——书未讨论。

8. 边界与局限

  • 童子军规则有过度执行的形态:每次提交都大改,会让代码评审失去焦点、diff 不可读(这些现代实践词不在书里,但机制的对称面在书中「一次性大幅修改很危险」处已经出现)33。分寸:改善是「顺手的一小步」,不是「顺手的大扫除」。
  • 技术负债这套比喻不覆盖「故意欠」的伦理面:什么时候该拒绝借债(比如安全相关的代码),书里没有讨论边界;第 07 章的「正当性」可作补充视角。
  • 破窗效应的实验可复现性:城市心理学的原始实验在后来的复现研究中有争议;「干净环境抑制破坏行为」的定性结论稳定,25% 这个数字应视为当年实验值而非普适常数(书内未提争议)。
  • 熵增是比喻不是定律:软件不像封闭系统,它可以被外部注入「负熵」(清理);类比的有效范围是「不管理则恶化」,不顺推成「必然恶化到死」。

9. 可带走的

  1. 把「代码会自己变坏」当默认值:不做干预的代码库,半年后一定更难改——这不是谁写坏了,是熵;
  2. 五个坏味背下来:重复、函数太长、模块太大、模块太多、名字过期——都是当场可查的;
  3. 借债可以,记账必须:欠的每一笔都要有还款计划;临时方案=负债,同账管理;
  4. 信用卡模式:上线后趁自己还记得,立刻回滚重写——还款日之前还清,利息为零;
  5. 修不了的烂代码,至少标出来:被记录的烂不是破窗,没记录的烂才是;
  6. 七征兆当季检表:刻板、脆弱、难移植、难掌控、复杂、重复、不透明——呈现即回查原则;
  7. 童子军规则:每次提交让代码净干净一点点——它是对抗熵的唯一可持续姿势;
  8. 给非程序员讲负债:别争「整洁是不是美德」,算「利息是几天工期」。

10. 原文地图

主题原书章原文位置
熵的定义与束缚7.4 熵增原理text/74-ch07.txt:271(搜「混乱程度」) · text/74-ch07.txt:273(搜「逃不出熵增原理」)
移动的靶子7.4 熵增原理text/74-ch07.txt:287(搜「移动的靶子」)
坏味定义、重构=体质优化4.5 代码中的坏味text/71-ch04.txt:640(搜「不祥的预兆」) · text/71-ch04.txt:648(搜「体质优化」)
光背目录没用4.5 代码中的坏味text/71-ch04.txt:662(搜「重构的目录」)
五坏味清单4.5 代码中的坏味text/71-ch04.txt:675(搜「代码重复」) · text/71-ch04.txt:683(搜「函数太长」) · text/71-ch04.txt:691(搜「模块太大」) · text/71-ch04.txt:699(搜「模块太多」) · text/71-ch04.txt:709(搜「名称不一致」)
拆分过度、名字过期4.5 代码中的坏味text/71-ch04.txt:702(搜「分解得太小」) · text/71-ch04.txt:714(搜「一成不变」)
技术负债定义4.6 技术负债text/71-ch04.txt:740(搜「背上了「债务」」) · text/71-ch04.txt:732(搜「欠款」)
利息与恶性循环4.6 技术负债text/71-ch04.txt:751(搜「利息」) · text/71-ch04.txt:761(搜「恶性循环」)
失去提供价值的能力4.6 技术负债text/71-ch04.txt:755(搜「无力偿还」)
信用卡模式、记录设计4.6 技术负债text/71-ch04.txt:787(搜「信用卡」) · text/71-ch04.txt:791(搜「本应编写的代码设计」)
讲给上司听4.6 技术负债text/71-ch04.txt:796(搜「技术负债这个比喻」) · text/71-ch04.txt:801(搜「争取到一些时间」)
技术病、慢且精4.6 技术负债text/71-ch04.txt:836(搜「技术病」) · text/71-ch04.txt:851(搜「慢且精」)
临时方案惯性法则4.6 技术负债text/71-ch04.txt:862(搜「惯性法则」) · text/71-ch04.txt:869(搜「长期留存」)
破窗与信箱实验7.3 破窗效应text/74-ch07.txt:209(搜「被遗弃」) · text/74-ch07.txt:220(搜「信箱实验」) · text/74-ch07.txt:222(搜「25%」)
「随便改一改算了」7.3 破窗效应text/74-ch07.txt:219(搜「随便改一改」)
不安机制7.3 破窗效应text/74-ch07.txt:231(搜「不安」) · text/74-ch07.txt:233(搜「更加敏感」)
立即修、登记管理7.3 破窗效应text/74-ch07.txt:239(搜「立即进行修补」) · text/74-ch07.txt:244(搜「带标签的注释」)
七征兆7.4 熵增原理text/74-ch07.txt:293(搜「刻板」) · text/74-ch07.txt:234(搜「脆弱」) · text/74-ch07.txt:315(搜「可移植性差」) · text/74-ch07.txt:320(搜「难以掌控」) · text/74-ch07.txt:335(搜「复杂指」) · text/74-ch07.txt:346(搜「重复指」) · text/74-ch07.txt:357(搜「不透明」)
预埋的反效果7.4 熵增原理text/74-ch07.txt:340(搜「相反的效果」)
童子军规则、量变质变5.2 童子军规则text/72-ch05.txt:136(搜「童子军」) · text/72-ch05.txt:157(搜「量变会引起质变」)
抄近道三清单5.2 童子军规则text/72-ch05.txt:171(搜「单元测试」) · text/72-ch05.txt:180(搜「与目的不符」) · text/72-ch05.txt:188(搜「不合适的库」)
软件老化四原因3.23 易变性text/29-ch03-23.txt:68(搜「软件老化」) · text/29-ch03-23.txt:70(搜「软件也会变老」) · text/29-ch03-23.txt:8(搜「轻松改良」)

Footnotes

  1. 出处:「7.4 熵增原理」第 271 段(text/74-ch07.txt:271,搜「混乱程度」)。

  2. 出处:「7.4 熵增原理」第 273 段(text/74-ch07.txt:273,搜「逃不出熵增原理」)。

  3. 出处:「7.4 熵增原理」第 282 段(text/74-ch07.txt:282,搜「维护难度不断增大」)。

  4. 出处:「7.4 熵增原理」第 287 段(text/74-ch07.txt:287,搜「移动的靶子」)。

  5. 出处:「3.23 易变性」第 8 段(text/29-ch03-23.txt:8,搜「轻松改良」)。原文:「易变性指软件能被轻松改良的能力」,具体要求是能被轻易修改、扩展、重组、移植。

  6. 出处:「扩展二 软件老化」第 70 段(text/29-ch03-23.txt:70,搜「软件也会变老」)与第 74 段(text/29-ch03-23.txt:74,搜「设计灵活性不足」)、第 81 段(text/29-ch03-23.txt:81,搜「更新停滞」)。四原因原文并列列出,第 81 段给结论:「我们无法阻止软件老化,但是我们能够减缓软件的老化速度」。

  7. 出处:「4.5 代码中的坏味」第 648 段(text/71-ch04.txt:648,搜「体质优化」)。

  8. 出处:「4.5 代码中的坏味」第 662 段(text/71-ch04.txt:662,搜「重构的目录」)。

  9. 出处:「4.5 代码中的坏味」第 675 段(text/71-ch04.txt:675,搜「代码重复」)、第 683 段(text/71-ch04.txt:683,搜「函数太长」)、第 691 段(text/71-ch04.txt:691,搜「模块太大」)、第 699 段(text/71-ch04.txt:699,搜「模块太多」)、第 709 段(text/71-ch04.txt:709,搜「名称不一致」)。

  10. 出处:「4.5 代码中的坏味」第 702 段(text/71-ch04.txt:702,搜「分解得太小」)。

  11. 出处:「4.5 代码中的坏味」第 714 段(text/71-ch04.txt:714,搜「一成不变」)与第 716 段(text/71-ch04.txt:716,搜「立刻更正」)。

  12. 出处:「4.6 技术负债」第 740 段(text/71-ch04.txt:740,搜「债务」)与第 732 段(text/71-ch04.txt:732,搜「欠款」)。

  13. 出处:「4.6 技术负债」第 751 段(text/71-ch04.txt:751,搜「利息」)。

  14. 出处:「4.6 技术负债」第 761 段(text/71-ch04.txt:761,搜「恶性循环」)。

  15. 出处:「4.6 技术负债」第 782 段(text/71-ch04.txt:782,搜「正确的选择」)。

  16. 出处:「4.6 技术负债」第 791 段(text/71-ch04.txt:791,搜「本应编写的代码设计」)。

  17. 出处:「技术负债用于说明代码整洁的意义」第 796 段(text/71-ch04.txt:796,搜「技术负债这个比喻」)与第 801 段(text/71-ch04.txt:801,搜「争取到一些时间」)。

  18. 出处:「技术病」第 851 段(text/71-ch04.txt:851,搜「慢且精」)。

  19. 出处:「临时解决方案」第 862 段(text/71-ch04.txt:862,搜「惯性法则」)与第 869 段(text/71-ch04.txt:869,搜「长期留存」)。

  20. 出处:「7.3 破窗效应」第 209 段(text/74-ch07.txt:209,搜「被遗弃」)。

  21. 出处:「7.3 破窗效应」第 214 段(text/74-ch07.txt:214,搜「让整个软件腐烂」)。

  22. 出处:「7.3 破窗效应」第 220 段(text/74-ch07.txt:220,搜「信箱实验」)与第 222 段(text/74-ch07.txt:222,搜「25%」)。

  23. 出处:「7.3 破窗效应」第 219 段(text/74-ch07.txt:219,搜「随便改一改」)。

  24. 出处:「7.3 破窗效应」第 231 段(text/74-ch07.txt:231,搜「不安」)与第 233 段(text/74-ch07.txt:233,搜「更加敏感」)。

  25. 出处:「7.3 破窗效应」第 239 段(text/74-ch07.txt:239,搜「立即进行修补」)。

  26. 出处:「7.3 破窗效应」第 244 段(text/74-ch07.txt:244,搜「带标签的注释」)。

  27. 出处:「7.4 熵增原理」第 293 段(text/74-ch07.txt:293,搜「刻板」)、第 234 段(text/74-ch07.txt:234,搜「脆弱」)、第 315 段(text/74-ch07.txt:315,搜「可移植性差」)、第 320 段(text/74-ch07.txt:320,搜「难以掌控」)、第 335 段(text/74-ch07.txt:335,搜「复杂指」)、第 346 段(text/74-ch07.txt:346,搜「重复指」)、第 357 段(text/74-ch07.txt:357,搜「不透明」)。

  28. 出处:「7.4 熵增原理」第 310 段(text/74-ch07.txt:310,搜「脆弱的模块并不罕见」)。

  29. 出处:「7.4 熵增原理」第 340 段(text/74-ch07.txt:340,搜「相反的效果」)。

  30. 出处:「5.2 童子军规则」第 136 段(text/72-ch05.txt:136,搜「童子军」)与第 140 段(text/72-ch05.txt:140,搜「更整洁」)。

  31. 出处:「5.2 童子军规则」第 157 段(text/72-ch05.txt:157,搜「量变会引起质变」)。

  32. 出处:「5.2 童子军规则」第 171 段(text/72-ch05.txt:171,搜「单元测试」)、第 180 段(text/72-ch05.txt:180,搜「与目的不符」)、第 188 段(text/72-ch05.txt:188,搜「不合适的库」)。

  33. 出处:「技术病」第 852 段(text/71-ch04.txt:852,搜「大幅度的修改」)。