4、技术债务清理:识别高风险模块、重构还是打补丁的决策模型、快速原型验证

4.1 先聊聊技术债务这回事

技术债务,说白了就是代码里埋下的「坑」。

我做了十几年嵌入式,见过太多项目被技术债务拖垮。不是功能做不出来,而是改不动、测不了、不敢动。你想想看,一个模块里if-else嵌套了七八层,全局变量满天飞,中断服务函数里还调了printf——这种代码,谁敢碰?

但项目进度已经失控了,你不可能停下来把所有代码重写一遍。那怎么办?

我的经验是:先识别,再决策,最后验证。三步走,缺一不可。

4.2 识别高风险模块:别靠感觉,靠数据

很多人喜欢说「我觉得这个模块有问题」。嗯,感觉有时候准,但更多时候会骗人。

我建议用三个量化指标来识别高风险模块:

指标 计算方法 高风险阈值
圈复杂度 代码中独立路径的数量 > 15
代码行数 单个函数/文件的代码行数 > 500行/函数
缺陷密度 该模块bug数 / 代码行数 × 1000 > 10个/KLOC

我在一个智能家居项目中遇到过这种情况:一个温控模块,圈复杂度高达28,代码行数1200行,缺陷密度15个/KLOC。三个指标全爆红。结果呢?每次改这个模块,至少引入2个新bug。后来我们花了3天重构,测试周期从2周缩短到2天。

识别高风险模块的快速方法:

  • 用静态分析工具(如PC-Lint、Coverity)跑一遍,看圈复杂度
  • 翻git提交记录,看哪个文件修改频率最高
  • 问测试同事:哪个模块的bug单最多?

4.3 重构还是打补丁?一个决策模型

这是最让人纠结的问题。重构怕时间不够,打补丁怕以后更麻烦。

我自己的决策模型是这样的:

先问三个问题:

  1. 这个模块还要活多久?——如果下个版本就要砍掉,打补丁就行
  2. 改动频率高不高?——如果一个月改一次,重构值得;如果半年不动,别折腾
  3. 测试覆盖够不够?——没有自动化测试,重构就是找死

根据答案,我一般这样决策:

场景 建议策略 我的经验
模块即将废弃 + 改动少 打补丁 别浪费时间,加个注释说明问题就行
模块核心 + 改动频繁 + 有测试 重构 值得投入,但一定要分阶段做
模块核心 + 改动频繁 + 无测试 先补测试,再重构 我曾经跳过这步,结果重构完功能全乱了
模块核心 + 改动少 打补丁 + 计划重构 先稳住,等有空了再处理

避坑指南:

我曾经在一个项目中,看到同事热血上头,说要「彻底重构」一个通信协议栈。结果重构了3个月,功能还没对齐,项目直接延期了2个月。后来我接手,用打补丁的方式先稳住,再逐步替换模块,反而只用了2周就搞定了。

记住:重构不是目的,解决问题才是。

4.4 快速原型验证:用最小成本试错

决策做完了,但你怎么知道这个方案可行?

我的习惯是:先做一个快速原型,验证核心逻辑。

什么叫快速原型?就是只实现最关键的功能,其他都砍掉。比如你要重构一个电机控制模块,原型只需要:

  • 能跑通主控制循环
  • 能处理3个核心状态
  • 能输出关键日志

其他的,比如异常处理、边界条件、性能优化,统统先不管。

我一般用这种方法:

// 快速原型示例:验证新的状态机逻辑
// 只实现核心状态,不处理异常

typedef enum {
    STATE_IDLE,
    STATE_RUNNING,
    STATE_STOP
} motor_state_t;

motor_state_t current_state = STATE_IDLE;

void motor_control_prototype(void) {
    switch (current_state) {
        case STATE_IDLE:
            // 只验证启动逻辑
            if (start_cmd_received()) {
                current_state = STATE_RUNNING;
                printf("[PROTO] IDLE -> RUNNING\n");
            }
            break;
        case STATE_RUNNING:
            // 只验证停止逻辑
            if (stop_cmd_received()) {
                current_state = STATE_STOP;
                printf("[PROTO] RUNNING -> STOP\n");
            }
            break;
        case STATE_STOP:
            // 只验证复位逻辑
            if (reset_cmd_received()) {
                current_state = STATE_IDLE;
                printf("[PROTO] STOP -> IDLE\n");
            }
            break;
        default:
            break;
    }
}

快速原型验证的3个要点:

  • 时间限制:原型开发不超过2天,超过说明范围太大了
  • 目标明确:只验证「这个方案能不能跑通」,不验证性能
  • 及时丢弃:原型就是原型,别想着复用。我见过有人把原型代码直接合进主线,结果全是坑

4.5 总结一下我的做法

技术债务清理,说白了就是三件事:

  1. 识别:用数据说话,别靠感觉。圈复杂度、代码行数、缺陷密度,三个指标一拉,高风险模块一目了然
  2. 决策:问三个问题——模块寿命、改动频率、测试覆盖。根据答案选策略,别盲目重构
  3. 验证:快速原型,2天出结果。行就继续,不行就换方案

嗯,这套方法我在好几个项目中都用过。不能说百分百成功,但至少没让项目因为技术债务而崩盘。你试试看,应该也能用得上。