重构不是重写:何时动手、怎么小步改善代码
“代码能跑就别动它”——这是程序员圈流传最广的谚语,也是无数代码库走向腐烂的起点。需求越加越多,代码结构越来越乱,改一个bug要翻半小时代码,加一个功能怕碰坏三个模块。直到某天有人提议”重写吧”,大家面面相觑:没人敢接这个锅。其实中间还有一条路,叫重构——马丁·福勒在经典著作《重构》中给出的定义是:在不改变代码外在行为的前提下,改进内部结构。它不是重写,不是打扫卫生,而是持续还技术债。这篇文章讲清楚重构的时机、方法和铁律。
为什么代码会”腐烂”
软件不是一次写成的,而是被无数次修改”长”出来的:需求变了、赶进度走了捷径、几个人风格混杂……这些过程不断积累技术债——重复的逻辑、含义不明的变量名、几百行的长函数、动一发而牵全身的耦合。技术债是要付利息的:每次改需求都更慢、更怕、更容易出bug。重构的本质就是还债:它不产生新功能,但让后续每个功能都做得更快更稳。福勒有个著名的判断:肮脏的代码必须重构,但漂亮的代码也需要重构——因为需求在变,昨天的”好设计”今天可能已经是负担。
三个最佳重构时机
- 预备性重构:加新功能之前。福勒的比喻是”我要往东走100公里,先把这段路修好”——新功能要落在那段最乱的代码上,先花半天理顺结构,新功能一天做完;直接硬塞,三天都未必能测完。
- 捡垃圾式重构:每次经过就变好一点。野营者的老话:”至少让营地比你到达时更干净。”每次因为需求或bug经过一段代码,顺手做一件小事——重命名一个变量、提取一段重复逻辑,积少成多,垃圾总会被清完。
- 修复bug时顺手清理:修bug的过程就是理解代码的过程,顺手把误导性的命名和死代码清掉,下次再来的人会感谢你。
反过来,也有不该重构的时候:一段代码虽然凌乱,但你根本不需要碰它,就不要去动——没有业务理由的重构是自嗨,还增加了回归风险。
重构的两条铁律:小步 + 测试
- 小步前进,改完就跑测试:每次只做一个动作——重命名、提取函数、移动字段、简化条件。改完立刻跑测试,绿了再走下一步。几十个微小的修改累积起来,就是一次完整的设计改善;而一次”憋大招”式的大改,出了bug根本定位不到是哪一步改坏的。
- 没有测试保护的代码,先补测试再动:福勒的答案是”没测试就加测试”。给核心路径补上自动化测试,重构才有安全网;没有测试的重构等于高空走钢丝。
另外记住:重构期间绝不添加新功能。重构的目标是”行为不变”,加功能是另一件事,两件事混在一起,出了问题你分不清是重构改坏的还是新功能引入的。动手前先识别”代码异味”——长函数、大类、重复代码、过长参数列表、散弹式修改,这些是重构的主要对象。
一个真实案例:3000行文件的救赎
小孙接手公司订单模块时,核心逻辑躺在一个3000行的文件里,没有任何测试,全组没人敢改,新需求只能”叠床架屋”。他没有申请”重写项目”,而是做了一件反直觉的事:先花一周给核心路径补了20个自动化测试,把”能跑”变成了”可验证”。之后他给自己立了规矩:每次需求或bug经过这个文件,顺手做一次小重构——提取重复的金额计算逻辑、把含义不明的”data1″重命名为”discountRate”、把散落的校验收拢成函数。三个月后,3000行的文件拆成了6个模块,新需求开发速度明显提升,线上bug率下降了一半。他说:”我从来没有一次’大重构’,我只是让代码每天变好一点点。”
新手最容易踩的3个坑
- 把重构当重写:推倒重来风险极高,表面”更干净”了,行为却在悄悄改变,而且重写期间业务还在迭代,两边永远对不上。
- 没测试就大改:等于没有安全网的高空作业。先补测试,再谈重构。
- 一次重构太多、范围失控:改动越大出错面越大。单次重构能在一小时内完成并跑绿测试,才是健康的节奏。
行动建议:本周就做这三件事
- 选一个你改动最频繁的模块,先为核心路径补上自动化测试——这是所有重构的前提。
- 下次改这段代码时,顺手做一件”捡垃圾”小事:重命名一个误导性变量,或删掉一段死代码。
- 用IDE的自动重构功能(重命名、提取函数)练习小步重构,感受”改一步、测一步”的节奏。