债务不是你觉得丑的那段代码,是每次改它都要多花的时间。
一、他先修了最丑的一条
陆远的一人 SaaS 跑到第 14 个月,代码 62,400 行,9 个模块。九月的一个周一,他打开自己维护的债务表,18 条,按「看着最乱」排了个序,第一条是 search 模块里一段 700 行的查询拼装。他花了 18 个小时重写,发布推迟了三天,还漏掉订阅状态的一个分支,第二天又补了两个小时。
以下情境为教学假设,人物、引语和数字不代表经核实的真实案例。
三个月后他回看这条债:那段代码一年只被改动两次,重写并没有让任何一次改动变快。他把它降到低档,年利息 0.8 小时,修复估价 18 小时,比值 0.04。同一张表里,真正每周都要碰的是权限判断:同一套「谁能看这个项目」的逻辑散在 projects、files、admin 三处,每加一种角色要改三个地方,平均多花 24 分钟。
债务的单位是时间,不是丑陋程度。 「这段代码很丑」不是一条可排队的债;「每次改它多花二十四分钟、一年改二十四次」才是。前者只能靠感觉排序,后者能算。
利息要按改动次数算,不按代码行数算。 700 行的查询一年被碰两次,利息不到 1 小时;30 行的权限判断一年被碰 60 次,利息 24 小时。行数说明阅读成本,改动频率才说明复利。
修复估价必须包含验证。 只算「改代码」的小时数,会把每条债都估得偏乐观;陆远的 6 小时里,有 2 小时是补测试与手工回归。
一个人还债的顺序由比值决定。 年利息除以修复小时,从高到低排队;比值低于 1 的债,修它不如先让它待着。
二、十八条债与六个字段
18 条 = 高 4 + 中 9 + 低 5。每条只登记六个字段:触发条件、影响模块、年利息、修复估价、证据、复查日期。字段少是为了让登记这件事本身不超过 10 分钟。
| 等级 | 条数 | 年利息合计 | 处理节奏 |
|---|---|---|---|
| 高 | 4 | 51 小时 | 本季度排期,按比值从高到低 |
| 中 | 9 | 7.2 小时 | 每季度挑两条,搭在相关功能里做 |
| 低 | 5 | 2.6 小时 | 只登记,不改;等下一条相关改动顺手处理 |
三档利息差得很远:4 条高危占了 51 小时,剩下 14 条合计 9.8 小时。这个分布说明一件事:还债的收益几乎全在头部,平摊到 18 条上去做,等于把 25 小时的有效修复换成 18 次半途而废。
触发条件决定这条债什么时候被看见。写成「当新增一种角色时」比写成「权限模块」有用,因为前者能在改动发生时自动提醒你,后者只会在你翻表时提醒你。
影响模块一栏要写具体模块名,不要写「核心」。写 projects 与 files,你才知道这次修复要不要跨边界;写「核心」,等于承认你自己也没想清楚它粘在哪。
三档的平均利息也不同:中档 9 条合计 7.2 小时,平均每条 0.8 小时;低档 5 条合计 2.6 小时,平均每条 0.52 小时。这个平均值决定了处理方式——中档值得搭车,低档只值得登记。
证据字段只收两种:一次提交号,或一个工单号。没有证据的条目一律先删掉,它多半是情绪而不是债。复查日期默认 90 天后,到期只回答一个问题:利息估高了还是估低了。
年利息的算法统一为「单次多花的分钟数 × 一年改动次数」。24 分钟 × 152 次 = 3,648 分钟 = 60.8 小时,这是 18 条债的总利息;单条债按它自己的触发频率折算,不按它占多少行折算。
三、四条高危与还债顺序
四条高危的形状不同,比值差 4 倍。按「年利息 ÷ 修复小时」排序,A 是 4.0,B 是 2.0,D 是 1.5,C 是 1.0。排序结果与「哪段代码最乱」几乎没有关系。
| 编号 | 债务形状 | 年利息 | 修复小时 | 利息÷修复 |
|---|---|---|---|---|
| A | 权限判断散在 projects、files、admin 三处 | 24 小时 | 6 小时 | 4.0 |
| B | 订阅状态在 4 处各自判断 | 12 小时 | 6 小时 | 2.0 |
| C | 报表查询缺索引,走全表扫 | 9 小时 | 9 小时 | 1.0 |
| D | 邮件模板硬编码在业务代码里 | 6 小时 | 4 小时 | 1.5 |
A 的比值最高,因为它的触发频率最高:每加一种角色、每改一次可见范围,三处都要动。C 的比值最低,不是因为不痛,而是因为修它要 9 小时,而它一年只让你多花 9 小时。
四条全修:25 小时换 51 小时/年,回本 0.49 年,约 5.9 个月。只修 A:6 小时换 24 小时/年,回本 0.25 年,约 3 个月。两个都划算,但先做 A 意味着同样的 6 小时早三个月开始产生收益。
架构预算一年 156 小时,占 2,080 小时的 7.5%。利息 60.8 小时加修复 25 小时是 85.8 小时,占预算的 55.0%;也就是说,把四条高危还完,一年里超过一半的架构预算就花掉了。剩下的预算要留给依赖升级与迁移回滚脚本。
四条高危的 51 小时占了总利息 60.8 小时的 83.9%,这就是「先修头部」的全部依据。后面 14 条即使全部修完,一年也只省 9.8 小时,不到 A 一条的一半。
红线放在工程时间的 5%,即 104 小时/年。当前利息 2.9% 没有触发;把修复也摊进这一年是 4.1%,再加四条各一次单独发布与观察的 8 小时(4 × 2),是 93.8 小时、4.5%,仍未到 104 小时。红线的作用不是现在停功能,而是提前告诉你「再登记两条高危就会到」。
四、复算:利息、回本与红线
总利息:24 分钟 × 152 次 = 3,648 分钟 ÷ 60 = 60.8 小时/年
改动次数:38 次/季 × 4 = 152 次/年
占比:60.8 ÷ 2,080 = 2.92% ≈ 2.9%
四条高危利息:24 + 12 + 9 + 6 = 51 小时
四条高危修复:6 + 6 + 9 + 4 = 25 小时
剩余 14 条:60.8 − 51 = 9.8 小时
校验:51 + 9.8 = 60.8 小时
回本(四条):25 ÷ 51 = 0.49 年 × 12 = 5.9 个月
回本(只修 A):6 ÷ 24 = 0.25 年 × 12 = 3.0 个月
架构预算:3 小时/周 × 52 = 156 小时/年
预算占比:156 ÷ 2,080 = 7.5%
利息 + 修复:60.8 + 25 = 85.8 小时
占预算:85.8 ÷ 156 = 55.0%
红线:2,080 × 5% = 104 小时/年
含修复占比:85.8 ÷ 2,080 = 4.13%
加 4 次发布观察:4 × 2 = 8 小时 → 93.8 ÷ 2,080 = 4.51% ≈ 4.5%
复算的第一层是利息,第二层是回本。利息回答「这条债一年吃我多少时间」,回本回答「我现在投进去的小时,多久能挣回来」。两个数都算出来,排序就不再靠印象。
51 与 9.8 这两个数要能加回 60.8,这是整张表最容易出错的地方。很多人把四条高危的利息单独加总后,忘了剩下的 14 条还在产生利息;一减一加对不上,说明有一条债被重复登记,或者有一条根本没登记。
回本周期要按年而不是按月比。0.49 年与 0.25 年的差别看起来小,但按两年看,前者回本约 4 次,后者约 8 次;同样是 25 小时与 6 小时的投入,排在前面那条的复利更快。
红线的意义在于提前量。等到利息真的到 104 小时,你已经被它拖慢了一个季度;4.5% 这个数的作用,是让你在还有 10 小时余量时就开始排期。
复算还有一个用途:判断什么时候该重估。半年后改动次数若从 152 升到 190,总利息就从 24 分钟 × 190 = 76 小时;利息表不是一次填完就作废的档案,触发频率变了,整张表都要重算。
中低档的 9.8 小时不是不用管,而是不该单独排期。它们的正确处理方式是搭车:下一次改 notifications 时顺手把邮件模板那条挪走,下一次动 analytics 时顺手补上索引。
五、行动清单与复算验收
- 把现有债务写成 18 行,每行只填六个字段,超过十分钟没填完的先删掉。
- 每条债的证据只留一个提交号或一个工单号,没有证据的条目不进表。
- 用「单次多花分钟 × 年改动次数」算出每条债的年利息,不估「大概很烦」。
- 把修复估价拆成改代码、补测试、手工回归三段,写在同一格里。
- 按「年利息 ÷ 修复小时」从高到低排序,把比值低于 1 的移到低档。
- 这个季度只排四条高危,按排序做,一次只动一条,做完更新证据与复查日期。
- 在日历上锁死每周 3 小时架构预算,还债从这 3 小时里出,不占功能时间。
- 每季度末复算一次总利息,超过 104 小时/年就停新功能两周。
验收标准:如果 18 条债的年利息加总不等于 60.8 小时,或者四条高危的修复小时加总不等于 25 小时,那么这张债务表还不能用来排期。
清单里最容易被跳过的是第四条。修复估价只写「6 小时」,做的时候才发现还要补 2 小时回归,下次排期就会系统性低估;把三段分开写,估错的概率会明显下降。
排序做完后不要一次改四条。一次只动一条,是因为改完要观察一周的改动耗时有没有真的下降;四条一起改,下降了你也不知道是哪条起的作用。
使用边界与实操验收
这套方法只适用于有稳定改动记录的代码库。 上线不到三个月、改动次数还没稳定的项目,年利息算不出来;这时候该做的是先把改动记下来,而不是先排债务。
利息口径不能跨项目复用。 24 分钟是一次改动的平均多花时间,它绑定这套 62,400 行的代码与这名开发者的熟悉度;换一个人、换一个代码库,必须重新测十次改动再定。
比值低于 1 的债不是不用还。 它们多数会随相关功能自然消失,或者在某次重构里被顺手带走;强行排期只会挤掉比值 4.0 的那条。
债务表不能替代架构决策。 它回答「先修哪条」,不回答「这个模块该不该拆」;后者要回到依赖方向与模块边界,用四层规则判断。
什么时候该停下来。 当修复估价连续三次超过实际耗时两倍,说明你对这块代码的理解已经不足,先补测试与文档,再谈还债。
关键参考与核验范围
核验日期:2026-10-26。以下文档用于核对技术债的概念、重构手法与架构决策记录,本文所有数字均取自全组教学基线,不取自这些文档。
- TechnicalDebt | Martin Fowler :用于核对「债务与利息」这一隐喻的原始含义,以及它不覆盖哪些情况。
- Refactoring catalog :用于核对重构手法的名称与适用前提,便于把「修一条债」拆成具体动作。
- Architecture decision records :用于核对决策记录的字段与存放方式,作为债务证据的补充。
- The Twelve-Factor App :用于核对配置外置与依赖声明的边界,避免把「配置散落」误登记成架构债。
引用口径。 文中 18 条、24 分钟、152 次、60.8 小时、25 小时全部取自全组教学基线;中档 7.2 与低档 2.6 是同口径拆出的分项,加回四条高危的 51 小时正好是 60.8;0.8 是低档中一条的利息,8 小时是四条高危各自单独发布与观察的额外成本,单列在修复一侧;外部文档只用于核对概念与手法名称,不提供任何工时基准。
核验范围。 上述四个入口在 2026-10-26 实测可公开访问(HTTP 200);未核验各页面的最近更新时间与所涉工具版本。
如何自行复核。 取最近一个季度,数出功能改动次数并乘以 4;再记录其中十条债各自让你多花的分钟数,按第二节的公式重算总利息。若与你表里的 60.8 小时差超过 20%,先查改动次数,而不是先怀疑单次分钟数。
系列导航
- 上一篇:依赖管理:四十一个直接依赖与四个升级窗口
- 下一篇:抽象与复用:第三次重复才抽,一次抽错的代价
- 回到目录:一个人的架构:技术选型、模块边界与演进
- 相关阅读:独立开发者生存指南(四):工程与技术策略 、重构还是重写:三张判据表与 160 小时对 48 小时 、系统架构设计专题
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。