那个 3000 行的 Service
接手交易核心模块时,OrderService.java 有 3128 行,下了 47 个 @Autowired 的 DAO 和远程 client。任何一处改动我都得屏住呼吸。上周一个改下单幂等的小需求,回归测试就跑了三轮,改动 8 行、提心吊胆一整天。更糟的是,新人根本不敢碰这个文件,Review 时也没人看得懂那几十个私有方法之间谁调谁。
这种"大泥球"不是一天长成的。五年里十几个需求往上叠,每个都"只加一个方法",没人愿意动存量。直到它成了团队的效率黑洞,我才下定决心重构。
怎么拆而不崩
我没敢一次性重写。核心系统重写等于赌命,一次发布把下单全量逻辑换掉,出问题就是资损。我采用了"保测试、切调用、拆内部"的节奏,每次只动一块,可回退。
第一步:先补测试当安全网
原类零测试。我先用 Spring 的 @SpringBootTest 把现有的对外 public 方法都罩一层冒烟测试,覆盖核心下单、取消、退款三个入口,约 32 个用例。这一步不重构任何逻辑,只为后面兜底。过程中反而发现了 2 个隐藏的边界 bug——这也证明这个类早就该有测试了。
测试跑通后,我给它套了一层"行为契约":给定输入,输出和落库结果必须和重构前一致。后面每一步提取,只要这 32 个用例还是绿的,就说明没改坏行为。
第二步:按职责提取
用 IDE 的 Extract Class 把耦合度低的几块先抽出去。提取顺序也有讲究,先抽"叶子"——不依赖其他业务方法、只被别人调用的:
- 价格计算 → PriceCalculator
- 库存扣减 → InventoryDeductor
- 优惠券核销 → CouponSettler
抽完 OrderService 还剩 1400 行,但逻辑清晰了很多,每个类单测也能独立写了。每抽一块,我就提交一次、灰度一个机房观察半小时,确认无异常再抽下一块。
第三步:引入编排层
把下单流程显式化成一个步骤链,而不是散落在方法里的 if-else。原来那个 createOrder 方法里,价格、库存、优惠券、落库、发消息是串在一坨的,中间还夹着一堆 try-catch 自己吞异常。
public class OrderPlaceSaga {
private final List<OrderStep> steps;
public OrderResult execute(OrderCmd cmd) {
OrderContext ctx = new OrderContext(cmd);
for (OrderStep s : steps) {
s.invoke(ctx); // 任一步失败抛异常,由上层统一回滚
}
return ctx.toResult();
}
}
每个 step 实现统一接口,成功就往下走,失败就抛,由外层事务统一回滚。这样"下单到底做了哪些事"一眼可见,也方便以后加步骤(比如风控)只插一个 step。
第四步:清掉私有方法的调用网
剩下的 1400 行里还有一堆互相调用的 private 方法。我用"搬移 + 内联"逐步归位:能归到某个领域的迁到对应组件,纯工具的直接提成静态方法。这一步最磨人,但每清一块,文件就瘦一圈。
节奏控制
整个过程分了 6 次提交、跨 3 周完成。每次只动一块,跑全量测试 + 灰度一个机房。重构期间线上零故障,回滚也只回一个提交。如果某次灰度发现 RT 异常,直接回退那次提交,不影响已上线的其他拆分。
有两点心得:一是绝不为了"优雅"顺手改逻辑,提取时保持行为不变,逻辑优化放到单独的后续提交;二是把重构拆成交付物,每步都有可验证的测试,这样领导也放心、自己也有底气。
重构中的测试策略
提取每个组件时,我同步补它的单测。PriceCalculator 这种纯逻辑,单测覆盖率拉到 90% 以上,这样后续谁改价格规则都有保护。集成测试则保留在 OrderService 对外的几个入口,确保"整体行为不变"。两层测试分工:单测管局部正确,集成管行为契约。重构期间这 32 个集成用例一次没红过,是最大的定心丸。
一个差点翻车的细节
第三步抽编排层时,我手滑把一个"发送下单成功消息"的步骤挪错了顺序,导致消息在库存扣减前就发了,下游收到消息去查订单发现还没落库。灰度一小时就被监控抓到下游空指针。因为灰度只放一个机房,回退那次提交就恢复了,没影响全量。这再次证明"小步提交 + 灰度"的价值——如果一次性全量上线,这 bug 得资损才知道。
重构后的收益量化
三个月后文件从 3128 行降到不到 400 行,方法平均长度从 60 行降到 18 行。新需求(加风控步骤)从"在 3000 行里找地方插"变成"加一个 OrderStep 实现",开发时间从预估 2 天降到 3 小时。Review 冲突也从动辄几十行变成几行。这些软收益,比单纯"代码好看"重要得多。
小结
大泥球不要试图一次洗干净。先把网织好,再一小块一小块切,每步都可回退,重构才敢在核心系统上做。测试是安全网,灰度是缓冲垫,节奏是护身符。三个月后那个文件从 3128 行降到不到 400 行,团队改需求的信心明显回来了。