“这块代码该重构了”很难争取到资源,它描述的是主观感受,而排期表上每一行都是可交付的功能。技术债管理的第一步不是动手重构,而是把感受翻译成能用同一套标准比较的东西——否则偿还永远排在交付之后,直到某天以故障的形式强制偿还。
债务先要分类
不同类型的债,利息形式和偿还方式完全不同,混在一起算总账必然失真。
| 类型 | 典型表现 | 主要利息形式 |
|---|---|---|
| 代码债 | 重复逻辑、超长函数、命名混乱 | 每次改动耗时增加、改动诱发新问题 |
| 架构债 | 边界模糊、循环依赖、模块职责重叠 | 新需求影响面扩大、无法局部发布 |
| 测试债 | 覆盖率低、测试依赖真实环境、用例不稳定 | 回归靠人工、发布恐惧、缺陷率上升 |
| 文档债 | 接口无说明、部署步骤口传 | 新人上手慢、故障处理依赖特定个人 |
| 依赖债 | 库版本过期、中间件不再维护 | 安全风险、升级成本随时间递增 |
分类的意义在于选出可偿还的部分。依赖债和架构债往往需要专项投入,代码债适合在日常开发中顺手处理。把”重构整个模块”当成一般任务排进迭代成功率极低——它太大、太难验收,通常会被压缩或延期。
还有一个常被忽略的类型:无知债——当时的最优解在今天看是错的,但那是约束条件下的合理选择。它不构成过失,也不宜用”欠债”追责。区分它与草率决策是债务管理能否被团队接受的前提:当时是否有更优的已知方案、是否处在明确时间约束下。
利息怎么算
不必追求精确的货币化,但要把”不好维护”变成可比较的量。
思路一是改动耗时比。同一个模块,一个熟悉它的人完成一次典型改动要多久,与结构清晰的模块对比,差距就是利息。估算可来自团队直觉——让三个人分别估再取中位数,比单人判断可靠。
思路二是故障概率。统计过去半年到一年的缺陷并按模块归类。高频问题若集中在一两个模块,那里就是债务最重的区域,数据从缺陷管理系统提取,成本低且难以被质疑。
思路三是新人上手时间:从入职到独立完成第一个生产改动需要多久。它综合了代码可理解性、本地环境搭建难度和文档质量,是很诚实的度量。
把这些记进统一的表格,每行对应一个具体的债务项:
| 债务项 | 类型 | 利息表现 | 量化方式 | 优先级 |
|--------------------|---------|---------------------------|---------------------|-------|
| 订单价格计算逻辑重复 | 代码债 | 每次调价要改 4 处,常漏改 1 处 | 改动 2h vs 同类 0.5h | 高 |
| 库存服务依赖订单库表 | 架构债 | 订单表变更需两个团队排期 | 涉及需求平均多 3 天 | 中 |
| 核心接口无契约测试 | 测试债 | 发布靠人工回归半天 | 每版本 0.5 人日 | 高 |
优先级列用高/中/低三档而不是百分制评分。估算精度本就有限,粗糙的分档比看起来精确的分数更耐用。
还有一条提醒:量化结果不要用于横向比较团队或个人。一旦债务数字变成评价依据,数字就会停止增长——不是因为债务减少,而是因为没人愿意记录它。
用可观测数据支撑判断
直觉会偏差,工程数据是更好的依据。把提交日志里的改动热点与缺陷系统里的缺陷密度组合起来,做一张二维图:
高缺陷密度
|
[需要重点关注] | [稳定且核心]
频繁改 + 常出错 | 频繁改但少出错
|
低频 ----------+---------- 高频
| 改动频率
[冷代码] | [需警惕的热点]
少改少错 | 频繁改但暂未出错
|
低缺陷密度
右上角(频繁改动且经常出错)偿还优先级最高:改动多说明它承载需求,错误多说明结构撑不住当前变化率。左下角的冷代码即使难看也不值得投入——它不在变化路径上,重构不产生收益。
数据提取很简单,版本控制的提交记录就够了:
# 统计近半年改动最频繁的文件
git log --since=6.months --name-only --pretty=format:
| sort | uniq -c | sort -rn | head -30
另一个有价值的信号是改动的影响面:一次需求平均改多少个文件、跨多少个服务。这个数随时间上升通常说明架构边界在退化——原本一个模块能完成的事,现在要穿透好几层。
这些数据不只是用来找债务,也能验证偿还效果:重构后同一区域的改动耗时是否下降、缺陷密度是否改善。没有事后验证,”重构完成了”只是说法。
偿还策略与配比
常见三种方式各有位置。
顺手还:每次改到某段代码就把它改好一点。成本最低,积少成多,缺点是不解决结构性问题——顺手改不动循环依赖。
专项还:针对具体债务立项,明确范围与验收标准,适合架构债和依赖债,但要专门争取资源。专项成本要设上限:给明确工期,到期必须能上线,否则容易变成无限期的重构工程。
随重构还:改某个功能时顺带调整结构。风险是功能改动与重构混在一起,出问题难以回滚。稳妥做法分两步提交——先做不影响行为的整理(提取函数、去重复、补测试),再做行为改动,分别上线观察。
配比没有标准答案,常见实践是给迭代预留一部分容量(例如 15%~20%)用于偿还。低于这个比例,偿还速度跟不上产生速度;长期需要更高比例,说明债务已到需要专项治理的程度。
优先级排序的标准只有一条:利息。先还利息最高的,而不是最刺眼的。所有人都抱怨的命名问题若位于不常改动的模块,优先级应低于只有两人知晓、但处在改动热点上的结构缺陷。
怎么向非技术管理者说
用”代码质量”这个词沟通几乎必然失败,它没有对应的业务影响。有效的翻译是把债务换成对方关心的量:交付速度与故障成本。
用交付速度说话。把同类型需求的交付周期做成趋势:三个月前类似需求平均五天,现在要八天。这个趋势本身就是债务的证据,而且能换算成人力成本——多出的三天乘以团队人日成本,就是一个可理解的金额。
用故障成本说话。列出近期几次故障的止损时长与影响量级,标注其中多少次与已知债务项直接相关。不必精确到”这段坏代码损失了多少”,只要能说明这两次故障的根因都在同一处结构上,就足以支撑投入的判断。
用机会成本说话。某个本可快速上线的新能力,因要绕过现有结构需要额外多久。
表达时的实用技巧是不问”要不要重构”,而是给选项:A 方案直接做新功能,交付周期 X,但同类问题三个月后会再出现一次;B 方案先花 Y 天处理结构,之后这类需求周期降到 Z。决策质量取决于选项质量,而不是取决于对方懂不懂技术。
还有一点要诚实:偿还债务的收益多数是避免未来的损失,而不是带来可见的增长。承认这一点比夸大收益更容易建立长期信任。也要承认另一面——不是所有债都要还,运行稳定且不在改动路径上的系统,维持现状往往是合理选择。判断要不要还,看的是利息是否真的在持续产生,而不是它是否符合当下认同的写法。
Comments (2)
我们公司禁用了这个方案,说是安全问题,看完大概知道原因了。
收藏夹又多了个吃灰的(不,这次真会看)。
Please Sign in to join the discussion.