重构不是推倒重来:小步重构如何做到随时可上线

接手过一段“祖传代码”的人,大概都闪过这样的念头:这代码实在太烂了,干脆推倒重写。但真正动过手的人大多后悔——重写半年,需求一直在变,旧系统还得继续跑,新系统遥遥无期。软件工程领域有一个被反复验证的结论:重构的正确姿势不是大动干戈,而是小步快走,让每一次改动都小到“错了也能立刻回头”。这篇文章想讲清楚两件事:为什么小步重构有效,以及怎么把它落到日常开发里。

重构的本质:不改变行为,只改善结构

《重构:改善既有代码的设计》给出的定义是:在不改变软件可观察行为的前提下,调整内部结构,提高可理解性、降低修改成本。注意两个关键词:一是“可观察行为不变”,重构不是加功能、不是修 bug,而是纯粹的结构调整;二是“内部结构”,用户感知不到,受益的是未来每一个读代码、改代码的人。

为什么要持续重构?因为代码的阅读成本远高于写作成本。一个八百行的函数、变量名全是 a/b/c 的模块,每次改需求都要花几倍时间“考古”。技术债最可怕的地方在于它会利滚利:越乱越不敢改,越不敢改越乱,最后没人愿意碰它,只能靠“人肉兼容”续命。而“小步”的意义在于:重构最大的风险不是改动本身,而是“出了问题却不知道是哪一步改坏的”。一次改动只做一件小事,每做完一步就运行测试、提交版本,出错时只需要检查最后一步,甚至一键回滚。风险被压缩到可以随时撤销的粒度,重构才敢频繁做、天天做。

第一步:先织一张“测试保护网”

没有测试就重构,等于走钢丝不系安全绳。但现实是很多老项目根本没有测试,怎么办?两个办法:一是从核心路径补起——支付、登录、下单这类“坏了就出大事”的逻辑,先用单元测试或接口测试把关键行为锁住;二是写特征测试(characterization test):不清楚代码本该干什么时,先记录它当前的输入输出,把现状固化成测试,重构之后只要输出不变就算过关。测试还有一个容易被忽视的要求:要快。全量测试如果跑四十分钟,没人愿意每次改动都跑,保护网就形同虚设;想办法把关键测试压缩到几分钟以内,重构的频率会肉眼可见地提升。

小步重构的三条纪律

纪律一:一次只做一件事。重构时不夹带新功能,加功能时不顺手重构——两种变更混在一起,出了问题都说不清是谁引起的,回滚也会误伤。

纪律二:每一步都可运行、可提交。遵循“测试保护—识别坏味道—套用重构手法—再测试”的循环,每一步的间隔以分钟计,而不是以天计。跑完测试、提交一次,心里才踏实。

纪律三:随时可以停下来。小步重构的魅力在于它不是一次“工程”,而是可以无限拆分的日常动作:今天只改一个函数名,明天只抽出一段重复代码,都算数。用“三次法则”判断时机:同一段代码第三次让你头疼时,就该动手了。

案例:一次三千行函数的手术

一个电商后台的订单结算函数长达三千行,优惠计算、库存扣减、日志记录全混在一起,每次改优惠规则都提心吊胆。团队没有选择重写,而是每周抽两个下午做小步重构:先把日志代码用“提炼函数”挪出去,再把优惠计算拆成独立方法,每拆一步就跑一遍已有的六十个结算测试。三个月后,这个函数变成八个职责清晰的模块,新增一种促销只需改一个文件。期间也出过岔子——一次提炼后测试变红,团队回滚那一步,十分钟内恢复。

对比隔壁组“两年重写核心系统”的经历:写到一半,业务规则已经变了两轮,项目最终废弃,团队元气大伤。两者差距不在技术能力,而在节奏——小步重构让改进与业务变化始终同步,改进永远追得上变化;大重写则赌“变化会停下来”,而业务从来不会。

常见误区

  • 误区一:没测试就动手。保护网不是可选项,没有它,小步也会在连锁反应里变成大步。
  • 误区二:重构时顺手加需求。混合变更会让排查和回滚都失去意义。
  • 误区三:把重构当一次性大工程,非要“彻底改造”才收手。完美主义是重构的头号杀手。
  • 误区四:分不清重构与重写。结构烂到根上时可以重写,但要用“绞杀者模式”逐步替换老模块,而不是一刀切换、孤注一掷。

行动建议

从今天开始立一条规矩:每次提交代码前,留十五分钟做一次“捡垃圾式”重构——改一个糟糕的命名、删一段重复代码、把一个过长的函数拆成两半。配合版本控制,你会发现重构根本不需要勇气,它只是一连串“错了也赔得起”的小决定。代码终将被重写,但清晰的结构能让你每一次修改都快人一步。

标签:#, #, #