重构与技术债务:量化与偿还技术债务的策略

技术债务这个词,我估计大家都听过。但说实话,很多人把它当成了一个“玄学概念”——知道它存在,却不知道怎么量化,更不知道怎么还。

我这些年接手过不少“历史遗留系统”,说白了就是技术债务爆仓的项目。代码烂得像一团乱麻,每次改需求都像在雷区里跳舞。嗯,今天我们就来聊聊,怎么把技术债务从“感觉”变成“数字”,再一步步把它还清。

什么是技术债务?

技术债务,说白了就是“为了短期速度,牺牲了长期质量”。

举个例子:你为了赶上线,写了一段没有单元测试的代码。当时是快了,但三个月后,别人改这段代码时,得花两倍的时间去猜逻辑。这就是利息。

我个人习惯把技术债务分成两类:

  • 主动债务:你明知道这样写不好,但为了赶工期,先欠着。比如“先硬编码,后面再改”。
  • 被动债务:你当时觉得写得挺好,但随着业务变化,代码变得不合适了。比如“当年用单表存所有数据,现在数据量大了,得拆表”。

我在项目中遇到过最典型的被动债务,是一个用了五年的报表系统。当年设计时,每天只有几百条数据,现在每天几十万条。查询慢得像蜗牛爬,但没人敢动——因为没人完全理解那几千行的存储过程。

如何量化技术债务?

量化是第一步。没有数字,你就没法跟产品经理说“这个需求需要先还债”。

我常用的量化方法有这几种:

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. 童子军规则

每次修改一个文件,都比来的时候干净一点。哪怕只是重命名一个变量,或者提取一个方法。

这个规则的好处是:不需要专门安排“重构时间”。把重构融入日常开发中,积少成多。

我的习惯:每次修改代码时,如果发现一个“坏味道”,顺手改掉。如果改动太大,就记在TODO里,下次专门处理。不要想着“一次搞定所有”,那只会让你陷入重构的泥潭。

3. 划定“红线”

有些债务,不能欠。比如:安全漏洞、数据一致性问题、核心性能瓶颈。

这些是“红线债务”,一旦发现,必须立即偿还。不能因为“赶工期”而妥协。

我曾经踩过的坑:一个支付系统,因为赶上线,把金额计算逻辑写在了前端。当时觉得“后端校验一下就行”。结果上线后,有人通过修改前端请求,绕过了校验,造成了资金损失。从那以后,我定了一条铁律:涉及钱、安全、用户隐私的代码,不允许有任何技术债务。

知识体系图

下面这张图,是我对技术债务管理的整体理解。你可以把它当作一个“作战地图”:

技术债务管理框架 技术债务 识别与量化 偿还策略 圈复杂度 代码重复率 测试覆盖率 静态分析告警 高利贷优先 童子军规则 划定红线 渐进式重构 目标:可量化的渐进式债务管理

一个实战案例

最后,分享一个我亲身经历的案例。

几年前,我接手了一个“用户中心”系统。代码量不大,但质量极差。我做了三件事:

  1. 量化:用SonarQube跑了一遍,发现圈复杂度超过20的函数有37个,代码重复率22%,测试覆盖率只有12%。
  2. 定优先级:找出被改动最频繁的5个函数,全部重写。同时,给核心业务逻辑(登录、注册、权限校验)补了单元测试。
  3. 定规则:新代码必须满足:圈复杂度<10,测试覆盖率>80%。不满足的,不允许合并到主分支。

三个月后,重复率降到8%,测试覆盖率升到65%,线上故障减少了70%。

你看,技术债务不是洪水猛兽。只要能量化、有计划、有策略,它完全可以被管理好。

核心观点:技术债务管理不是“一次性大扫除”,而是“日常保洁”。每天清理一点,比一年大扫除一次,效果好得多。


公众号:蓝海资料掘金营,微信deep3321