- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 引言 — 削减83%,以及39.7美分
- 新实验实际测量了什么 — 以及作者本人附上的警告
- 既有证据比想象中单薄
- 计算回本周期 — 变更频率是那个相乘项
- 利息这个比喻,哪里成立、哪里崩塌
- 说服经理的方法 — 用git历史,不用审美
- 结语 — 代码只在被读的时候才计利息
- 参考资料
引言 — 削减83%,以及39.7美分
2026年7月30日,Thoughtworks的Giles Edwards-Alexander在martinfowler.com上发表了The Economic Benefit of Refactoring。这个实验设计干净利落。他从一个完全由AI代理搭建的15万行应用中,挑出一个有问题的17,155行Rust数据访问模块,固定了一句要求「代表性变更」的提示词,然后分15步对模块进行重构 — 每一步都重新跑一遍同样的提示词 — 并测量token消耗。步骤之间会丢弃改动,不让代理从中学习。
结果非常适合拿来引用。输入token从159,564个降到27,360个,减少了132,204个,也就是83%。而Hacker News讨论里几乎同时冒出来的反驳也一样适合引用 — 按当时的价格算,这笔节省相当于39.7美分。对比一位时薪100美元的资深开发者花在指挥、监督这次重构上的8小时,得攒够几千次变更才能回本。
两个数字都没错。而这组对比恰好说明了,为什么重构这场争论吵了二十年也没个结论 — 成本看得见,收益却散落在未来,数收益的方式一变,答案就会翻过来。 本文要讲的正是这套数法:新实验到底测的是什么、既有证据有多薄弱、回本周期怎么算、利息这个比喻在哪里失效,以及在经理面前,用什么样的数字取代美学论证。
新实验实际测量了什么 — 以及作者本人附上的警告
先把这个实验测量的对象说准确。它测的不是人类的理解时间,而是代理的输入token — 也就是「完成这次变更需要读多少代码」的代理指标。正是这个定义让这个实验变得有意思。人类第二次读同一个文件时会变快,但代理每次都从零开始读,所以结构的成本会诚实地记在每一次变更的账上。在人类实验里因为学习效应而难以剥离的信号,在这里却清清楚楚地显现出来。
数字里还有几点值得读一读。
带来最大削减的是按领域拆分文件的那一步。15步里的最后一步,单独就把输入token从107,205个拉低到27,360个。作者在这里附了一条重要警告 — 随意把文件切得更碎并没有多大帮助,因为代理反而要在更多文件之间来回翻找。前面的步骤(比如提取重复代码这类局部整理)是让最后这次拆分得以成立的准备工作。换句话说,效果集中出现在最后一步,但这一步没法单独拿出来做。
输出token几乎没什么变化(大约1,700到2,460个之间)。这说明重构降低的是「读的成本」,对「写的成本」没有影响,作者本人在这一点上也没下结论。
而且代码总量并没有减少。最大的文件从17,155行降到了9,269行,但整个数据访问层横跨19个文件,总量仍然维持在大约16,500行左右,基本没变。Hacker News上有条评论专门指出了这一点,认为这和「重构通常会减少代码量」的常识相悖。
作者自己附上的警告相当长,值得原样搬过来。
- token的计数方式是近似值。他用的是字符数除以4的算法,并写明当时没有可靠的方法能实时精确计数token。Hacker News上有反驳说「明明有正经的分词器库,为什么不用」。
- 这是针对一个绿地应用、一名开发者、一个模块、一种变更类型的单次实验。
- 制定重构计划并执行所花的token没有计入。他估计上限在500万token左右,但这不是一个经过验证的数字。
- 而最有意思的一条警告是 — 代理并不擅长重构。 它没法自己判断该做哪种重构,每一步都得靠人明确下指令;实际的转换工作是用grep和sed写的Python脚本完成的,而这个脚本经常在缩进上出错。价值最大的那次重构一开始被漏掉了,后来不得不重新补做一遍。
说到底,这篇文章证明的不是「重构划算」,而是首次尝试为基于代理的开发提出一种衡量结构成本的方法。作者本人也写道,这「只是一次实验,但是有意思的第一步」。这样理解才对。
既有证据比想象中单薄
「重构划算」这个说法背后,撑着的是四十年的直觉,和少得惊人的定量证据。真要把值得引用的东西列出来,这份清单相当短。
被引用得最多的,是Adam Tornhill与Markus Borg的Code Red: The Business Impact of Code Quality(2022)。这项研究针对39个商用生产代码库、30,737个文件,把源码分析、版本控制历史与Jira工单结合起来,报告了三个结论 — 质量低的代码缺陷多15倍,工单解决平均要多花124%的时间,最坏情况下的周期时间能拉长到9倍。
这项研究很严谨,但局限也很明显。衡量代码质量用的是作者们自己开发的商业工具给出的指标,而且这种关系是相关,不是因果 — 本来就难啃的问题领域,同时催生出又乱又长的代码和解决时间,这种可能性并没有被排除。更关键的是,这项研究测的是低质量代码的成本,不是重构的收益。两者不是一回事:重构自己也有成本,也有回归风险。
值得引用的还有Martin Fowler本人的立场。在Is High Quality Software Worth the Cost?(2019年发表,2024年引用Tornhill与Borg的研究做了更新)一文中,他一边提出设计耐力假说,一边承认 — 没有办法衡量一个软件团队交付了多少功能,所以没法给这个结果按上一个确凿的数字。他给出的那张图是示意,不是数据。在这个领域里,最具影响力的主张,其作者本人却把自己证据的性质讲得这么清楚,这份坦诚反而更值得信任。他补充的一条实务观察很有用 — 熟练的开发者感受到糟糕代码明显拖慢了自己,往往只需要短短几周。质量和成本能够互相置换的那个窗口期,本来就不长。
反方向的证据也确实存在。 一项关于重构与缺陷之间关系的差异化复现研究(ESEC/FSE 2020)报告说,特定类型的重构 — 尤其是触及继承层级的操作 — 经常与引入新缺陷相关联。MSR 2022的Is refactoring always a good egg?发现,重构大多数时候要么消除了代码异味,要么什么影响都没有,这和以往的研究结论不一致。更新的研究则指向一个方法论问题 — 重构、修bug、引入bug这几件事往往缠在同一个提交里,光是把效果拆分开来就很难。
老实说,可以这样总结:低质量代价高昂是有证据的;重构能收回这笔成本却缺乏普遍性证据;某些类型的重构还会增加风险。 所以讨论不能从「重构是好事」这个前提出发,而是需要逐案计算。
计算回本周期 — 变更频率是那个相乘项
证据单薄的时候,能用的办法不是泛泛而谈,而是针对具体模块做计算。需要的项只有五个。
年度节省分两支。一支是变更成本的节省,另一支是缺陷成本的节省。
年度节省 = (C × T × S × H) + (D × F × K)
C : 该模块每年被改动的次数 (从git历史中提取)
T : 每次变更平均耗费的时间 (从issue跟踪系统中提取)
S : 其中因结构问题而浪费掉的比例 (估算。0.2~0.5属于保守)
H : 每小时人力成本
D : 该模块每年产生的缺陷数
F : 每个缺陷的总成本(响应 + 返工 + 客户影响)
K : 预计重构能减少的缺陷比例 (估算。0.3属于乐观)
重构成本 = (R × H) + (P × F_reg)
R : 重构所花的工时(含补充测试)
P : 引发回归的概率 × 预计发生次数
F_reg : 一次回归的成本
回本周期(月) = 重构成本 / (年度节省 / 12)
这里在结构上最关键的一点是,C是一个相乘项。 C如果是零,不管T、S、H多大,节省都是零。这正是「不会变的代码不值得整理」的数学表达。五年前写好、从此没人碰过、还照样跑得好好的脏模块,永远不会回本。审美上碍眼和经济上亏本,是两个不同的问题,这条公式正好把它们分开。
拿三个模块套一下这条公式,差别立刻变得很鲜明。这个例子把每小时人力成本设为₩100,000,每个缺陷设为₩2,000,000,S取0.3,K取0.3。这里的重构成本只算了人力这一项,回归风险那一项被省掉了,所以实际的回本周期会比下面这些数字更长。
| 模块 | 年度变更 C | 单次耗时 T | 年度缺陷 D | 年度节省 | 重构成本 | 回本周期 |
|---|---|---|---|---|---|---|
| 订单处理(热点) | 84次 | 6小时 | 11起 | 约₩21,720,000 | 160小时 = ₩16,000,000 | 约9个月 |
| 报表生成器 | 12次 | 8小时 | 2起 | 约₩4,080,000 | 120小时 = ₩12,000,000 | 约35个月 |
| 遗留结算批处理 | 1次 | 20小时 | 0起 | 约₩600,000 | 200小时 = ₩20,000,000 | 约33年 |
第三行才是这张表的重点。遗留结算批处理大概是整个代码库里读起来最痛苦的文件,也大概是团队里被吐槽次数最多的技术债。可它偏偏是碰不得的代码 — 回本周期比这个服务本身的寿命还长。
编这些数字时要注意三件事。S要老老实实取低一点。 把Code Red里的124%直接换算成S会得到0.55,但那是相关数据给出的上限,不是你这个模块的真实值。不要把P设成零。 前面提到的那些反方向研究,说的正是这一项 — 尤其是触及继承层级的重构,务必加上风险溢价。要是算出来的结论是「别做」,那就接受这个结论。 这套计算的价值不在于帮你拿到批准,而在于帮你决定不该把力气花在哪里。
利息这个比喻,哪里成立、哪里崩塌
「技术债」这个说法是Ward Cunningham在1992年造出来的,如今已经成了这场讨论的基本词汇。这个比喻确实做对了两件事 — 它把「现在图快、以后多付」这套结构翻译成了财务语言,也正因如此,这个论证在工程圈以外的人听来才站得住脚。光凭这一点,它就已经足够有用。
只不过,它在三个地方会崩塌。
第一,利息计的是接触,不是时间。 金融债务就算放着不动也会累积利息,但技术债只有在你碰那段代码的时候才会被计入。Cunningham本人的原话是「你花在不太对劲的代码上的每一分钟,都是那笔债的利息」 — 条件是花时间,不是时间流逝。这正是上一节里C是相乘项的原因,也正因为这一个差别,「债欠得越早还越好」这条金融直觉,放到代码上就是错的。
第二,本金算不出来。 没有利率,没有还款计划表,也没有到期日。有些工具会把债务总额换算成一个金额显示在仪表盘上,但很少有人能说清楚那个数字的分母到底是什么。一旦把比喻错当成模型,这个指标就会变成需要被管理的东西,而被管理的指标就会开始被优化。
第三,它不是线性的。 处在高耦合位置的债务,比纯局部的债务贵得多。同样是100行乱代码,摆在20个模块都依赖的地方和摆在角落里,成本完全不一样。金融比喻里根本没有「位置」这个概念。
还有一个误解,Cunningham本人纠正过好几次 — 他说的债,并不是打算「以后再好好做」而故意写得很随便的代码。 他指的是先快速上线、换取对领域的理解,再把这份理解反映回代码里的重构,如此循环。也就是说,在最初的比喻里,债务不是「烂代码」,而是尚未被反映进代码的认知。 这个定义在实务上更有用,因为它直接告诉你该整理什么 — 我们现在对这个领域的理解,和代码所假设的东西,两者产生分歧的地方。
说服经理的方法 — 用git历史,不用审美
「这段代码很乱」没有说服力。听的人没办法验证,这句话只会被当成一个口味问题归档。换个做法,把上一节公式要用到的值,从真实数据里取出来 — 这些全都是可审计的数字。
# 1) 变更频率 C:过去12个月按文件统计的提交次数,取前20
git log --since="12 months ago" --name-only --pretty=format: \
| sed '/^$/d' | sort | uniq -c | sort -rn | head -20
# 2) 与缺陷相关的变更 D:只筛出修bug的提交,做同样的统计
# (如果有提交规范就用 --grep,没有就用issue编号的模式)
git log --since="12 months ago" --name-only --pretty=format: \
--grep='^fix' --grep='hotfix' --grep='BUG-' \
| sed '/^$/d' | sort | uniq -c | sort -rn | head -20
# 3) 热点:在变更频率高的文件里,只留下体量大的
git log --since="12 months ago" --name-only --pretty=format: \
| sed '/^$/d' | sort | uniq -c | sort -rn \
| awk '{ n=$1; f=$2; cmd="wc -l < " f; cmd | getline loc; close(cmd);
if (loc > 500 && n > 20) printf "%5d changes %6d loc %s\n", n, loc, f }'
把这份输出原样带过去,谈话的性质就变了。「订单处理模块过去12个月被改了84次,其中23次是修bug,31%的P2事故都会经过这个文件」 — 这是一句可以核实的陈述。再给它配上一个回本周期,这就不再是一个请求批准的申请,而是一份投资提案。
接下来要配上的是一次划定范围的实验。 别要求整体大扫除,而是提议给一个热点分配固定的时间,测量前后的交付周期(lead time)。失败了,损失就止步于这个范围;成功了,就有了下一轮的依据。前面那个新实验对实务的启发也在这里 — 把同一个变更请求在重构前后各跑一遍,是最便宜的测量方法。用代理的话,token数就是代理指标;只靠人力的话,到第一次提交为止的时间就是。
最后,不要想着在争论里赢。算过三个模块、把其中两个判为「不做」、只提议剩下一个的人,会比那种坚持「全都得整理」的人快得多拿到批准。
结语 — 代码只在被读的时候才计利息
总结一下。
- 新出的这个实验用15步重构,把同一个变更的输入token削减了83%,但换算成钱,也就是每次变更39.7美分,而且作者本人明确说这只是单次的绿地实验。应该把它当成一种测量方法的提议,而不是当成结论。
- 既有证据很单薄。「低质量代价高昂」有相关性证据支持(缺陷多15倍,解决时间多124%),但「重构能收回这笔成本」缺乏普遍性证据,而且某些类型的重构还与引入缺陷相关。
- 所以别泛泛而谈,按模块逐个计算。因为变更频率是相乘项,不会变的代码,不管多乱都不会回本。
- 利息这个比喻,经理听得进去,但它不是模型。利息计的是接触而不是时间,本金算不出来,而且随耦合度呈非线性。
- 说服要靠git历史,不要靠审美。三个候选里自己刷掉两个、只提一个的方案,通过得最快。
压缩成一句话:重构的价值不取决于代码现在的状态,而取决于这段代码今后还会被读多少次。
参考资料
- Giles Edwards-Alexander — The Economic Benefit of Refactoring (martinfowler.com, 2026-07-30)
- Hacker News —《The Economic Benefit of Refactoring》讨论帖(含方法论批评)
- Tornhill & Borg — Code Red: The Business Impact of Code Quality (arXiv:2203.04374)
- Martin Fowler — Is High Quality Software Worth the Cost? (2019年发表,2024年更新)
- On the relationship between refactoring actions and bugs: a differentiated replication (ESEC/FSE 2020)
- Is refactoring always a good egg? (MSR 2022)
- Agile Alliance — Introduction to the Technical Debt Concept (Cunningham比喻的本意)
- 用AI搬迁代码库的实战流程 — 衡量指标篇(相关文章)