老化与保养 — 腐坏的征兆与还债
这一章讲五件事: 怎么「闻」出代码的坏味;技术负债的利息怎么滚; 一扇破窗怎么传染整栋楼;熵(物理学里「混乱程度」的量度)为什么只增不减、 七个征兆怎么自查; 以及被反复验证有效的日常对策——童子军规则。 主走查:一笔技术负债从借入到还清(和还不清)的两条时间线。
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,每条都值得当自查项:
- 刻板:一处修改,牵动所有依赖模块——效应局部化失效的直接读数;
- 脆弱:一改这里,别处坏;「越修复质量越差的模块」是它的现形记28;
- 可移植性差:想把它挪个环境,处处是粘连;
- 难以掌控:做错容易做对难——连「编译太慢」都会逼人抄近道,环境恶化也是腐坏燃料;
- 复杂:为想象中的未来预埋的结构——「很遗憾,这样做只会带来相反的效果」29;
- 重复:第 03 章的主角;看似相同实有细微差别=没做抽象化的铁证;
- 不透明:难以理解,且越改越难懂。
对照第 03、04 章会发现:七征兆没有一条是新病——重复、复杂、刻板, 全是前面原则失效后的「临床表现」。这份清单的价值不在新知识, 在于它把前面所有原则的失效形态集中给了你:当系统开始呈现这些症状, 回去查的一定是某条原则在哪次修改中缺席了。
6. 童子军规则:被验证有效的日常对策
这一节回答:对抗熵,个人每天具体该做什么。
美国童子军有条规则:「离开营地时,营地必须比来时更干净」——哪怕脏不是你弄的。 移植到代码:离开之前,让代码比你接手时更整洁,「代码最初是谁写的并不重要」30。
它的力量在复利方向与熵相反。熵的账本上,每次修改加一点混乱; 童子军规则把每次修改变成「顺手减一点混乱」——调整变量名、拆大函数、去一处重复, 「不管改善了什么,改善了多少,都没有关系」,关键是量变引起质变31。 所有人的每次提交都净减一点乱,系统的熵曲线就被掰弯了。
作者还用它反推了「抄近道」的后果清单,三条都值得记住32: 省掉单元测试(针对单个部件的小段自动检查)→ 以后每次修改都要手动全测,软件脆弱; 强行使用不合目的的现有系统 → 早晚返工,成本反增; 发现库不合适却将就 → 只能层层包裹遮丑,「软件不可能进一步得到优化」。 结论一句话:凡是与代码有关的抄近道,维护阶段都会连本带利地还回去。
7. 作者的判断与证据
书里给了证据的:
- 信箱实验(25% 盗率)是心理学界真实发表过的著名实验,书内给出数字与出处方向;
- 「坏味/重构」出自福勒《重构》,「负债/破窗/熵」出自《程序员修炼之道》,概念链有明确源头。
作者的推测与展开,要分开看的:
- 利息滚动的具体速度没有量化——本书只给机制,不给曲线;我们主走查里的天数是演示;
- 「人对长时间低强度压力更敏感」引心理学论断,在代码场景的适用性是作者的类比延伸;
- 「信用卡模式」的可行性依赖团队允许「上线后再发一版修正」,某些发布节奏下做不到——书未讨论。
8. 边界与局限
- 童子军规则有过度执行的形态:每次提交都大改,会让代码评审失去焦点、diff 不可读(这些现代实践词不在书里,但机制的对称面在书中「一次性大幅修改很危险」处已经出现)33。分寸:改善是「顺手的一小步」,不是「顺手的大扫除」。
- 技术负债这套比喻不覆盖「故意欠」的伦理面:什么时候该拒绝借债(比如安全相关的代码),书里没有讨论边界;第 07 章的「正当性」可作补充视角。
- 破窗效应的实验可复现性:城市心理学的原始实验在后来的复现研究中有争议;「干净环境抑制破坏行为」的定性结论稳定,25% 这个数字应视为当年实验值而非普适常数(书内未提争议)。
- 熵增是比喻不是定律:软件不像封闭系统,它可以被外部注入「负熵」(清理);类比的有效范围是「不管理则恶化」,不顺推成「必然恶化到死」。
9. 可带走的
- 把「代码会自己变坏」当默认值:不做干预的代码库,半年后一定更难改——这不是谁写坏了,是熵;
- 五个坏味背下来:重复、函数太长、模块太大、模块太多、名字过期——都是当场可查的;
- 借债可以,记账必须:欠的每一笔都要有还款计划;临时方案=负债,同账管理;
- 信用卡模式:上线后趁自己还记得,立刻回滚重写— —还款日之前还清,利息为零;
- 修不了的烂代码,至少标出来:被记录的烂不是破窗,没记录的烂才是;
- 七征兆当季检表:刻板、脆弱、难移植、难掌控、复杂、重复、不透明——呈现即回查原则;
- 童子军规则:每次提交让代码净干净一点点——它是对抗熵的唯一可持续姿势;
- 给非程序员讲负债:别争「整洁是不是美德」,算「利息是几天工期」。
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(搜「轻松改良」) |