两顶帽子重构法
总结
- 重构和加功能必须严格分开,不能同时进行
- 第一顶帽子:只调整结构,不改行为,测试全部通过
- 第二顶帽子:在整理好的结构上加新功能
- 小步提交,重构 commit 和功能 commit 分开
1. 先说结论
两顶帽子来自 Martin Fowler 的《重构》,核心就一句话:重构和加功能不能同时进行,必须严格分开。
两顶帽子分别是:
- 第一顶:只调整代码结构,不改行为,测试全部保持通过
- 第二顶:在整理好的结构上加新功能
2. 为什么要分开?
边重构边加功能是最常见的坑:
- 出了 bug 不知道是重构引入的还是新功能引入的
- 测试挂了不知道从哪查
- review 的人也看不懂这个 PR 到底在干嘛
分开之后,每个阶段目标单一,出问题好定位,也好回退。
3. 怎么做?
3.1 第一顶帽子:整理结构
只做这些事:
- 抽方法、抽类,消除重复代码
- 改命名,提高可读性
- 引入设计模式(策略、模板方法等)解耦变化点
硬性要求:改完之后所有测试必须通过,外部行为不能变。
// 重构前:支付逻辑写死
public class OrderService {
public void pay(Order order) {
alipayClient.pay(order);
}
}
// 重构后:抽出策略接口,解耦支付方式
public interface PaymentStrategy {
void pay(Order order);
}
public class AlipayStrategy implements PaymentStrategy {
public void pay(Order order) { alipayClient.pay(order); }
}
public class OrderService {
private PaymentStrategy paymentStrategy;
public void pay(Order order) { paymentStrategy.pay(order); }
}
3.2 第二顶帽子:加新功能
结构整理好了,加功能就很简单:
// 直接实现新的策略,不动核心代码
public class WechatPayStrategy implements PaymentStrategy {
public void pay(Order order) { wechatPayClient.pay(order); }
}
4. 实践建议
小步提交:重构的 commit 和功能的 commit 分开,PR 也分开,方便 review 和回退。
先有测试再重构:没有测试覆盖就不敢动,先补测试再戴第一顶帽子。测试不是必须 TDD,但重构前得有基本的覆盖。
别过度重构:第一顶帽子只服务于当前要加的功能,不要为了"以后可能用到"去做大规模改造。