技术债到底要不要还?先搞清楚这笔债的利息是谁在付

🔑 关键词:技术债,代码重构,遗留系统,可维护性,SonarQube

📖 摘要:技术债这个词被用烂了。Ward Cunningham 发明它的时候讲的是认知不确定,现在变成了指责烂代码的道德工具。这篇文章用 Stripe 的数据、SQALE 的公式和我自己踩过的三个坑,聊聊什么时候该重构、什么时候该忍着。

2019 年我在一家做 SaaS 的公司,团队接手了一个 2014 年立项的后台系统。第一次开 sprint 计划会,Tech Lead 在白板上画了根横轴,左边写「现在」,右边写「重构完成」,中间标了 6 周。他说这 6 周能把技术债清掉一大半。后来的事实是:6 周变成了 11 周,代码行数从 9.4 万降到 6.8 万,AbstractXXXService 这种名字删掉四十多个,然后我们上线第一个新功能,发现要的工时跟重构前一模一样。

图片

这件事我记了很久。它推翻了我对技术债这个概念的基本假设——我一直以为债还清了速度就会回来,但这次没有。

图片

技术债是 Ward Cunningham 在 1992 年 OOPSLA 的经验报告《The WyCash Portfolio Management System》里提出来的。他当时引入的是金融里的复利概念,大意是第一次交付时如果对领域的理解不到位,等于借了一笔钱,后续每次改动都在付利息。有意思的是 Cunningham 本人后来对这个比喻的传播效果很不满意,他在多个访谈里说过类似「我可能不该用债务这个词」的话,因为他本意是「先交付、再学习」,而不是给后人一把道德尺子。但今天你去任何一个技术团队,技术债几乎总是一个贬义词。它出现的时候往往伴随着指责:谁写的烂代码、哪个组不肯重构、老板不给时间。一个描述认知不确定性的比喻,变成了一个描述懒惰的比喻,这个错位是后面所有麻烦的源头。

更麻烦的是它被量化了。2011 年前后,法国一家叫 Inspearit 的公司里,Jean-Louis Letouzey 提出了 SQALE 方法(Software Quality Assessment based on Lifecycle Expectations),后来被 SonarQube 收进去做成了默认质量模型。核心公式很简单:Technical Debt Ratio = 修复成本 / 重新开发成本。SonarSource 默认给每行代码定价 30 分钟,再按规则违反的严重程度加权折算。你打开任何一份 Sonar 报告都会看到一个百分比,比如「技术债比率 4.7%,预计修复 21 天」。问题是这个数字完全是构造出来的。30 分钟一行是拍脑袋定的常数,我至今没找到任何实证研究支撑它;把「圈复杂度超标」和「空 catch 块」放在同一个加权体系里也说不通;不同模块的债直接相加更是没有任何道理。可它偏偏是个能写进季度 OKR 的数字,于是就有了我在第二个团队看到的事:为了把 Sonar 的 debt ratio 从 4.8% 压到 3% 以下(因为某个合规审计要求),两个工程师花了一个月删掉所有 TODO 注释、把重复的 DTO 强行抽象成一个泛型类。三周后业务方要加一个字段,那个泛型类炸了,改动量是原来的 4 倍。

图片

Stripe 在 2018 年 9 月出过一份叫 The Developer Coefficient 的报告,和调研公司 Harris Poll 合作,问了 1000 多名开发者和技术管理者。里面的数字是:开发者平均每周花 17.3 小时处理坏代码、技术债和维护问题,占一周工作时间的 42%。同一年 DORA 的 Accelerate 报告也在讲类似的事。这些数字是真的,但我不信它们能被拿来论证「应该多重构」,因为这个口径太宽了。一个人花三小时搞清楚一段 legacy 的计费逻辑为什么这么写,这算技术债的利息吗?我倾向于认为不算——那更像是理解一个陌生领域的必要成本。你重构完之后,新代码里还是会有同样的领域复杂度,只是换了个地方存放。

图片

我的看法是:技术债的利息不是代码本身,而是别人理解这段代码时付出的时间;而理解成本主要来自领域假设,不来自代码风格。一段 200 行的 if-else,如果它精确对应了「哪些客户能享受折扣」的真实规则,那它比一个漂亮的策略模式更好改,因为改代码的人能在同一次阅读里拿到完整信息。反过来,你把这段 if-else 拆成 DiscountStrategy、CustomerTier、AccountAgePolicy 三个类,规则本身一个字没变,只是从集中但丑变成了分散但好看,新人要跑三次 debug 才能把链路拼起来。这也解释了我开头说的那件事:那 11 周里我们做的绝大部分工作是让代码符合某个抽象标准,而真正降低理解成本的部分——把那 12 个没有文档的定时任务写清楚、把埋在 500 行方法里的限流逻辑提出来——大概只占了两周。

图片

如果要给一个能操作的说法,我会这么讲:

  • 判断一笔债该不该还,只问一个问题:改动这个模块的时候,有没有人问过「这段为什么这么写」,而且问了三次以上都没人答得上来?有,那是债。只是「看着不爽」,那不是。
  • 重构的收益按删除的行数算,不要按新增的抽象算。Martin Fowler 在 2009 年那篇 TechnicalDebtQuadrant 里把债分成四类(鲁莽/谨慎 × 有意/无意),我觉得还漏了一个维度:这笔债是阻止了后续改动,还是仅仅恶心了阅读者。前者要还,后者可以先挂着。
  • 别信 debt ratio。那个数字是为了卖许可证和写周报存在的。你要看就看 DORA 那四个指标里的两个——变更前置时间和部署频率——它们至少跟业务结果有相关性。想测代码质量,用 onboarding 时间比用 Sonar 靠谱:让一个新人接手某个模块,看他多久能安全地改一个小需求,这个数字比任何静态分析报告都诚实。

图片

最后说个私心。我现在听到「先还技术债再开发新功能」就会有点警惕,因为这句话的潜台词是「我们现在的慢是因为代码丑」。但我待过的四个团队里,慢的原因分别是产品需求没想清楚、测试环境不稳定、没人在意线上错误、以及关键人物休了三个月产假。代码质量只在其中一个里排到第三。技术债是个特别方便的替罪羊,因为它具体、可见、而且不会反驳你。

🏷️ 标签: