代码重构基础:从“能跑就行”到“优雅维护”
各位同学好,我是老张。今天咱们聊聊重构这个话题。
说实话,我见过太多嵌入式项目了。一开始大家赶进度,代码写得飞快。等产品上市了,客户要加新功能,这时候一看代码——好家伙,自己都看不懂了。这时候你怎么办?重写?还是重构?
嗯,这就是我们今天要讲的核心问题。
什么是重构?
重构,说白了就是在不改变外部行为的前提下,改善代码内部结构。
我习惯用一句话来记:“改结构,不改功能”。
举个例子,你写了一个窗帘电机的PWM控制函数:
// 重构前
void set_speed(int s) {
if(s > 100) s = 100;
if(s < 0) s = 0;
TIM3->CCR1 = s * 10;
TIM3->CCR2 = s * 10;
}
这个函数能跑,但问题不少。参数校验、限幅、寄存器操作全混在一起。重构一下:
// 重构后
#define MAX_SPEED 100
#define MIN_SPEED 0
static int clamp_speed(int speed) {
if(speed > MAX_SPEED) return MAX_SPEED;
if(speed < MIN_SPEED) return MIN_SPEED;
return speed;
}
static void set_pwm_duty(int speed) {
int duty = speed * 10;
TIM3->CCR1 = duty;
TIM3->CCR2 = duty;
}
void set_speed(int speed) {
speed = clamp_speed(speed);
set_pwm_duty(speed);
}
看到了吗?功能完全一样,但代码清晰多了。每个函数只做一件事,这就是重构。
核心要点:重构是“小步快跑”的渐进式改进,不是推倒重来。
重构 vs 重写:别搞混了
这个问题我经常被问到。很多工程师一看到烂代码就想重写,我劝你冷静。
| 对比项 | 重构 | 重写 |
|---|---|---|
| 改动范围 | 局部、渐进 | 全局、彻底 |
| 风险等级 | 低(可随时回退) | 高(可能引入新bug) |
| 时间成本 | 分散在开发周期中 | 需要整块时间 |
| 对业务影响 | 几乎无影响 | 可能中断业务 |
| 适用场景 | 代码结构差但逻辑正确 | 架构彻底过时或技术栈更换 |
我在项目中遇到过这种情况:一个同事接手了前任的烂摊子,看了三天代码后说“这没法改,重写吧”。结果重写了两个月,新代码的bug比旧代码还多。你想想看,旧代码虽然丑,但它在生产环境跑了两年,各种边界情况都处理过了。重写?等于重新踩一遍坑。
避坑指南:我曾经见过一个团队,把整个电机控制算法重写了三遍,每次都是因为“原来的代码太烂”。最后项目延期半年。记住:重写是最后的手段,不是第一选择。
什么时候该重构?
这个问题没有标准答案,但我有几个经验法则:
- 添加新功能时——如果现有代码让你很难下手,先重构再添加
- 修复bug时——如果同一个bug反复出现,说明代码结构有问题
- 代码审查时——发现坏味道(重复代码、过长函数、过大的类)
- 技术债务积累到一定程度——比如每次改代码都要花半天理解逻辑
我个人习惯是“三次原则”:同一个地方我改了三次,就说明这里需要重构了。
小技巧:不要专门安排“重构周”。把重构融入日常开发,每次改代码时顺手做一点。就像打扫房间,每天收拾五分钟,比周末大扫除轻松得多。
重构的收益与风险
收益这块,我直接说干货:
- 可维护性提升——代码更容易理解和修改
- bug率下降——清晰的代码不容易藏bug
- 开发效率提高——找问题、加功能都快了
- 团队协作顺畅——新成员上手更快
但风险也不能忽视:
- 引入新bug——重构过程中可能破坏原有逻辑
- 时间投入——短期内看不到产出
- 范围蔓延——本来只想改一个函数,结果改了整个模块
- 团队抵触——有人觉得“能跑就别动”
我的建议:重构一定要有测试保护。没有单元测试的重构,就像走钢丝不带安全绳。我在嵌入式项目中,至少会写一个简单的功能测试脚本,确保重构前后行为一致。
重构的黄金原则
最后,分享几条我这些年总结的原则:
- 小步提交——每次只改一个点,改完就测试
- 保持可运行——每一步改完,代码都能编译通过、功能正常
- 不要同时重构和加功能——两件事分开做,否则出了问题你都不知道是重构导致的还是新功能导致的
- 尊重历史代码——不要觉得旧代码都是垃圾,它能在生产环境跑这么久,一定有它的道理
嗯,今天就聊这么多。下一章我们讲具体的重构手法,到时候我会拿窗帘电机的实际代码来演示。记住:重构不是炫技,是为了让代码更好维护。咱们做嵌入式开发的,最终目标是把产品做好,不是把代码写得多花哨。