一、重构初探:什么是代码重构?
先说说我自己的经历吧。刚入行那会儿,我总觉得代码能跑就行。直到有一次,一个客户需求变更,我花了整整三天才改完一个模块。改完之后,又冒出来三个新bug。当时我就想,这代码是不是哪里不对劲?
后来我才明白,问题出在代码结构上。那堆代码就像一团乱麻,牵一发而动全身。这时候需要的,就是重构。
1.1 重构的定义
重构,说白了就是在不改变代码外部行为的前提下,改善其内部结构。注意关键词——「不改变外部行为」。也就是说,功能不能变,测试用例得全过。
重构的核心:让代码更容易理解、更容易维护、更容易扩展。
举个例子,你有一段这样的代码:
// 重构前
int calc(int a, int b, int c) {
int x = a * b;
int y = x + c;
int z = y / 2;
return z;
}
这段代码能跑,但变量名毫无意义。重构一下:
// 重构后
int calculateAverage(int price, int quantity, int discount) {
int subtotal = price * quantity;
int totalAfterDiscount = subtotal - discount;
int average = totalAfterDiscount / 2;
return average;
}
功能完全一样,但可读性天差地别。这就是重构的威力。
1.2 重构 vs 重写
很多人分不清重构和重写。我见过一个团队,明明只需要重构,结果一激动把整个模块重写了,工期翻了三倍。
| 维度 | 重构 | 重写 |
|---|---|---|
| 范围 | 局部调整 | 整体推翻 |
| 风险 | 低,逐步进行 | 高,一次性替换 |
| 时间 | 数小时到数天 | 数周到数月 |
| 外部行为 | 不变 | 可能变化 |
| 适用场景 | 代码结构差但逻辑正确 | 架构彻底腐烂 |
我曾经踩过的坑:有一次我试图重构一个3000行的函数,改到一半发现根本理不清逻辑。最后不得不全部重写。所以,重构也要看情况——如果代码已经烂到无法理解,重写可能是更务实的选择。
二、重构的时机
什么时候该重构?这个问题我思考了很久。后来总结出三个信号:
2.1 三次法则
我第一次写某个功能时,直接写。第二次遇到类似需求,我复制粘贴改一改。第三次再遇到,我就知道——该重构了。
为什么是三次?因为前两次你还不确定这个模式会不会重复。第三次基本可以确认,这是个通用逻辑。
2.2 添加新功能时
你想想看,每次加新功能都要在旧代码上打补丁,代码会越来越臃肿。我个人的习惯是:在添加新功能之前,先把相关的老代码重构好。这样新功能加进去,代码反而更清爽。
2.3 修复bug时
bug往往藏在混乱的代码里。如果你发现一个bug很难定位,或者修完一个又冒出来三个,那说明代码结构有问题。这时候别急着修bug,先重构。
我的经验:修bug时如果花了超过30分钟还没找到根因,我就会停下来,先把那段代码重构一遍。通常重构完,bug自己就暴露了。
三、重构的原则
重构不是乱改。我总结了几条铁律:
3.1 小步提交
每次只改一点点。改完就编译、测试、提交。不要想着一次改完所有问题。
// 错误示范:一次改了10个函数
// 正确做法:每次只重构1个函数,确保测试通过
3.2 测试先行
没有测试的重构,就是耍流氓。我见过太多人重构完发现功能全崩了。所以,重构之前,先把测试用例写好。
核心原则:没有测试覆盖的代码,不要重构。先写测试,再重构。
3.3 保持功能不变
重构过程中,如果发现需要修改功能,立刻停下来。那叫「重写」,不叫「重构」。把功能变更和结构改善分开做。
四、知识体系总览
下面这张图,是我对本章内容的总结。你可以把它当作一个思维导图来看:
五、避坑指南
最后,分享几个我亲身踩过的坑:
- 不要边重构边加功能。我曾经同时做这两件事,结果代码改到一半,功能也乱了,两边都没做好。
- 不要追求完美。重构的目标是「更好」,不是「完美」。够用就行,过度重构反而浪费时间。
- 不要一个人偷偷重构。团队协作时,重构要通知大家。不然别人还在用旧接口,你这边已经改完了,冲突一堆。
一个小技巧:重构之前,先把当前代码的git版本号记下来。万一改砸了,还能快速回退。我吃过这个亏,现在每次重构前都先打个tag。
嗯,关于重构的基础知识,就先聊到这儿。记住一句话:重构不是炫技,而是为了让代码更健康。下次你看到一段代码觉得「不对劲」的时候,别忍着,动手重构吧。