刚工作那几年,我把技术债理解成一个道德问题。看到烂代码就来气,觉得写的人不负责任,觉得项目就是被这些人拖垮的。我甚至私下列过一个清单,记录哪些模块该重构、谁写的。现在回头看,那个清单挺幼稚的。
让我改主意的一段代码
有一年我接手一个模块,里面有个硬编码的映射表,把十几个业务代码映射成显示名称,直接写在源文件里,一个巨大的字面量。我第一反应是:这什么玩意,应该放数据库或者配置里。我把它列进了重构清单。
结果两年过去了,我没动它,它也没出过任何问题。这两年里那张表一共改过三次,每次改动是加一行、提一个合并请求、跟着日常发布上线,全程不到十分钟。而如果我当初把它挪到数据库,就得多一张表、一套增删改查、一个缓存、一个缓存失效逻辑,还得考虑数据没同步时怎么兜底。
那一刻我意识到:它根本不是债,它是一个定价合理的决策。我之前判它有罪,用的标准是「它不够优雅」,而不是「它让我付出了什么代价」。
债的利息在改动里,不在阅读里
现在我判断一处代码是不是技术债,只看一个东西:改它的成本。更具体一点,是这三个数:
- 最近半年它被修改过几次?
- 每次修改从开始到上线平均花多久?
- 修改之后引发线上问题的比例是多少?
一段丑陋但从来不改的代码,利息为零。它可能长得让人不舒服,但它躺在那里安安静静地工作,你重构它得到的收益也是零,还要承担改坏的风险。反过来,一段看起来很正常、命名也规范的代码,如果每次改都要通知三个团队、要人工验证十个场景、上线后还要盯两小时,那它的利息高得吓人,哪怕它没有一行是丑的。
我们曾经有个订单状态流转的类,看起来干干净净,方法都不长。但它被六个入口调用,每个入口对状态的假设都不一样,改任何一条分支都可能影响另外五个。那半年我们改了它十一次,出了三次线上问题。这才是真正该还的债。
不是所有债都要还
我现在给债分三种处理方式:
- 还本金:重构。只对高频修改且改动成本高的地方做,而且要有明确的收益预期,比如「改完之后新增一种状态不需要动主流程」。
- 只付利息:不重构,但加护栏。补测试、加日志、写一段说明放在文件顶部告诉后来人这里为什么长这样、哪些改动是危险的。成本低,效果好,我用得最多。
- 宣告破产:明确承认这块烂,但决定永远不改它,直到整个模块下线。这需要写下来并且团队达成共识,否则每个新人都会重新纠结一遍。
还有一点自我反省
我以前特别爱说「这是历史包袱」,说的时候还带点优越感。后来自己写的东西也变成了别人口中的历史包袱,才明白当年那些人多半也不是不懂,只是当时手上的约束我看不见——上线时间、人手、一个已经改不动的上游接口。
技术债这个比喻好就好在「债」这个字:借钱本身不可耻,可耻的是不知道自己借了多少、什么时候还。我现在提交那种明知不完美的实现时,会在合并请求里写一句「这里我选了简单方案,代价是 X,如果将来出现 Y 就需要改成 Z」。这句话不值钱,但半年后它救过我好几次。