重构与技术债务:量化与偿还技术债务的策略
技术债务这个词,我估计大家都听过。但说实话,很多人把它当成了一个“玄学概念”——知道它存在,却不知道怎么量化,更不知道怎么还。
我这些年接手过不少“历史遗留系统”,说白了就是技术债务爆仓的项目。代码烂得像一团乱麻,每次改需求都像在雷区里跳舞。嗯,今天我们就来聊聊,怎么把技术债务从“感觉”变成“数字”,再一步步把它还清。
什么是技术债务?
技术债务,说白了就是“为了短期速度,牺牲了长期质量”。
举个例子:你为了赶上线,写了一段没有单元测试的代码。当时是快了,但三个月后,别人改这段代码时,得花两倍的时间去猜逻辑。这就是利息。
我个人习惯把技术债务分成两类:
- 主动债务:你明知道这样写不好,但为了赶工期,先欠着。比如“先硬编码,后面再改”。
- 被动债务:你当时觉得写得挺好,但随着业务变化,代码变得不合适了。比如“当年用单表存所有数据,现在数据量大了,得拆表”。
我在项目中遇到过最典型的被动债务,是一个用了五年的报表系统。当年设计时,每天只有几百条数据,现在每天几十万条。查询慢得像蜗牛爬,但没人敢动——因为没人完全理解那几千行的存储过程。
如何量化技术债务?
量化是第一步。没有数字,你就没法跟产品经理说“这个需求需要先还债”。
我常用的量化方法有这几种:
1. 代码复杂度指标
圈复杂度(Cyclomatic Complexity)是我最常用的指标。一个函数的圈复杂度超过15,基本就是“坏味道”了。超过30,我建议直接重写。
举个例子:
// 圈复杂度:12
public function processOrder($order) {
if ($order->status == 'pending') {
if ($order->amount > 1000) {
if ($order->user->level == 'vip') {
// 处理VIP大额订单
} else {
// 处理普通大额订单
}
} else {
// 处理小额订单
}
} else if ($order->status == 'shipped') {
// 已发货处理
} else if ($order->status == 'cancelled') {
// 取消处理
}
// ... 更多分支
}
这种代码,每次加新状态,都得小心翼翼。我曾经在一个类似的项目里,加了一个“退款中”状态,结果漏了一个分支,导致线上订单状态错乱。嗯,教训深刻。
2. 代码重复率
重复代码是技术债务的“利息放大器”。我见过一个项目,同样的分页逻辑在20个文件里各写了一遍。后来要改分页样式,改到崩溃。
工具推荐:SonarQube、PMD、或者简单的grep + wc。我个人习惯设定一个阈值:重复率超过15%,就需要专项治理。
3. 测试覆盖率
没有测试的代码,就是“裸奔”。我见过太多“改一行代码,崩三个功能”的案例。
我的经验是:核心业务逻辑的测试覆盖率,不能低于80%。低于60%,基本就是“技术债务高危区”。
| 指标 | 健康 | 警告 | 危险 |
|---|---|---|---|
| 圈复杂度(单函数) | < 10 | 10 - 20 | > 20 |
| 代码重复率 | < 5% | 5% - 15% | > 15% |
| 测试覆盖率 | > 80% | 60% - 80% | < 60% |
| 静态分析告警数 | < 50 | 50 - 200 | > 200 |
技术债务的偿还策略
量化完了,怎么还?我见过两种极端:一种是“不管不顾,继续堆代码”;另一种是“停掉所有业务,重构三个月”。两种都不可取。
我建议的策略是“渐进式偿还”:
1. 高利贷优先
先还“利息最高”的债务。怎么判断?看这个模块被改动的频率。
如果一个模块每周都被改,而且代码质量很差,那它就是“高利贷”。每次改动都在产生新的利息。这种模块,值得花时间重构。
我曾经在一个电商项目中,发现“订单状态机”模块每周被改3次,但代码里全是if-else。我花了两个周末,把它重构为状态模式。重构后,每次改状态逻辑的时间从半天缩短到半小时。这笔投资,值。
2. 童子军规则
每次修改一个文件,都比来的时候干净一点。哪怕只是重命名一个变量,或者提取一个方法。
这个规则的好处是:不需要专门安排“重构时间”。把重构融入日常开发中,积少成多。
3. 划定“红线”
有些债务,不能欠。比如:安全漏洞、数据一致性问题、核心性能瓶颈。
这些是“红线债务”,一旦发现,必须立即偿还。不能因为“赶工期”而妥协。
知识体系图
下面这张图,是我对技术债务管理的整体理解。你可以把它当作一个“作战地图”:
一个实战案例
最后,分享一个我亲身经历的案例。
几年前,我接手了一个“用户中心”系统。代码量不大,但质量极差。我做了三件事:
- 量化:用SonarQube跑了一遍,发现圈复杂度超过20的函数有37个,代码重复率22%,测试覆盖率只有12%。
- 定优先级:找出被改动最频繁的5个函数,全部重写。同时,给核心业务逻辑(登录、注册、权限校验)补了单元测试。
- 定规则:新代码必须满足:圈复杂度<10,测试覆盖率>80%。不满足的,不允许合并到主分支。
三个月后,重复率降到8%,测试覆盖率升到65%,线上故障减少了70%。
你看,技术债务不是洪水猛兽。只要能量化、有计划、有策略,它完全可以被管理好。
核心观点:技术债务管理不是“一次性大扫除”,而是“日常保洁”。每天清理一点,比一年大扫除一次,效果好得多。
公众号:蓝海资料掘金营,微信deep3321