功能迭代越来越慢、改一处坏三处,往往不是需求太复杂,而是代码内部结构已经腐化。重构的答案是:在保持外部行为不变的前提下,用小步、可验证的结构调整持续还债,而不是攒到推倒重写。

什么是重构

重构(Refactoring)是对软件内部结构的调整:不改变可观察行为,只让代码更易理解、更易修改。同样输入产生同样输出,这一条把它和功能开发区分开;只动结构,这一条把它和性能优化区分开——别打着重构旗号做优化。

日常开发有个”两顶帽子”心法:写功能时只加代码不改旧结构,重构时只调结构不加功能,两顶帽子别同时戴。

什么时候该重构:坏味道清单

坏味道(Bad Smell)是代码里”闻着不对”的信号,最常见的四种:

  1. 重复代码:同一段逻辑出现在多处,改需求要改好几遍,漏改一处就是 bug。
  2. 过长函数:函数翻几屏才看完,读者脑中要维持的上下文太多,通常意味着承担了多个职责。
  3. 过大类:十几个字段、几十个方法什么都要管,职责边界模糊后任何改动都可能牵连它。
  4. 霰弹式修改:加一个需求要在十几个类里各改一小处,说明某种职责被打散到各处,修改像霰弹一样四处开花。

闻到味道不必立刻停工。合适时机:加功能前先清理阻碍点,完成后顺手整理临时结构,Code Review 发现的问题即刻修或记待办。核心是见缝插针,而不是专门排”重构月”。

安全网:测试先行

重构的全部勇气来自一句话:改完跑一遍测试,绿了就证明行为没变。没有测试的重构等于蒙眼走钢丝。

动刀前先确认目标代码有快速可靠的测试:单元测试反馈最快,集成测试兜底协作。遗留代码没测试就先补特征测试——不为验证正确性,只为钉住当前行为(哪怕含错误行为),再动结构。测试代码与生产代码同等重要,测试烂了安全网就破了。

小步快走

正确重构的核心是步子小:每次只做一种结构调整,改完立即编译、跑测试、提交,出问题秒级回滚,随时能停。反面典型是憋大招:闷头改两周,测试全红、行为漂移,只能整段丢弃。步子小到”每步都不会错”,重构就从中高危操作变成日常动作。

常见重构手法

提炼函数(Extract Function)

把一段意图明确的代码抽成独立函数,用函数名说清”做什么”:

// 重构后:细节各归其位,函数名即注释
void printSummary(Order order) {
printBanner();
printDetail("客户: " + order.getCustomer().getName());
}

搬移函数与字段(Move Function/Field)

某个函数跟另一个类的数据交流比跟本类还频繁,说明它住错了地方——搬到数据所在的类里。霰弹式修改的解法正是反向收拢散落职责。

以多态替代条件表达式(Replace Conditional with Polymorphism)

switch 按类型分派时,每加一种类型都要回来改条件。把各分支提炼为子类重写的方法,新增类型只需新增子类:

abstract class Bird { abstract double speed(); }
class Penguin extends Bird { double speed() { return 0; } }

什么时候不该重构

刚学了设计模式就硬套、进度紧张时去动无关底层模块、代码烂到重写更省事——这三种情况建议收手。重构必须为项目带来真实价值。

小结

  • 重构 = 行为保持 + 结构改善,与功能开发、性能优化是两顶不同的帽子。
  • 重复代码、过长函数、过大类、霰弹式修改是四类最常见的坏味道。
  • 测试是安全网,没测试先补特征测试;每次只做一种调整、立即验证、小步提交。