第18章:遗留系统重构策略

说实话,每次听到「遗留系统」这个词,我都能感受到不少开发者的焦虑。我自己也经历过——接手一个跑了七八年的老系统,代码像迷宫一样,文档早就过时了,连原作者都离职了。你想想看,这种系统你敢随便动吗?

但问题来了:业务还在跑,需求还在变,技术债迟早要还。那怎么办?这一章,我就结合自己的实战经验,聊聊四种最实用的遗留系统重构策略。

18.1 遗留系统的典型特征

先别急着动手重构。第一步,你得先认清你面对的是什么样的系统。我在项目中总结过,遗留系统通常有这几个特征:

  • 代码耦合严重:一个类几千行,一个方法干十件事。改一个地方,崩三个模块。
  • 缺乏测试覆盖:没有单元测试,甚至没有集成测试。你敢重构?没人敢保证不出问题。
  • 技术栈老旧:还在用 jQuery 1.x、Struts、EJB 2.0 这些古董。新人不愿意碰,老人也快忘了。
  • 文档严重缺失:业务逻辑全靠口口相传。我见过最夸张的,连数据库字段含义都没人说得清。
  • 构建部署复杂:手动部署、依赖混乱、环境不一致。每次上线都像在拆弹。

核心观点:遗留系统不是不能重构,而是不能「硬重构」。你需要策略,而不是蛮力。

18.2 接缝策略(Seam)

这个概念来自 Michael Feathers 的《修改代码的艺术》。说白了,接缝就是代码中那些你可以「插入」改动的地方,而不需要大规模修改原有代码。

举个例子。假设你有一个老旧的订单处理类:

public class OrderProcessor {
    public void process(Order order) {
        // 直接调用第三方支付网关
        PaymentGateway gateway = new PaymentGateway();
        gateway.charge(order.getAmount());
    }
}

这个类直接 new 了一个 PaymentGateway,你想替换成新的支付服务?难。因为代码里到处都是这种硬编码依赖。

接缝策略的做法是:先找到「接缝点」,比如构造函数、工厂方法、或者接口。然后通过依赖注入或者提取接口,把硬编码变成可替换的。

public class OrderProcessor {
    private PaymentService paymentService;

    // 这就是一个接缝:通过构造函数注入
    public OrderProcessor(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void process(Order order) {
        paymentService.charge(order.getAmount());
    }
}

我的经验:我曾经在一个金融项目中,花了整整两周时间,只做了一件事——给一个老系统的核心模块「找接缝」。两周后,我们才敢开始动代码。别嫌慢,这一步省不了。

18.3 特征开关(Feature Toggle)

特征开关,也叫功能开关。它的核心思想是:把「发布」和「上线」解耦。代码可以部署到生产环境,但功能是否对用户可见,由开关控制。

为什么要用这个?因为遗留系统重构往往需要「灰度」验证。你不敢一下子全量切换,万一出问题怎么办?

一个简单的实现示例:

public class PaymentRouter {
    private FeatureToggle toggle;

    public PaymentRouter(FeatureToggle toggle) {
        this.toggle = toggle;
    }

    public void route(Order order) {
        if (toggle.isEnabled("new-payment-gateway")) {
            newPaymentService.charge(order);
        } else {
            oldPaymentService.charge(order);
        }
    }
}

注意:特征开关不是银弹。开关多了,代码里全是 if-else,反而增加复杂度。我建议:开关数量控制在 5 个以内,并且每个开关都要有明确的「退役计划」。

我曾经见过一个项目,上线了 30 多个特征开关,最后没人敢关,也没人知道哪些开关还在用。嗯,这比不改还糟糕。

18.4 绞杀者模式(Strangler Fig)

这个名字很形象。绞杀榕是一种植物,它从外部包裹住宿主树,慢慢取代它。这个模式用在系统重构上,就是逐步用新系统替换旧系统的功能模块,直到旧系统完全被取代。

具体怎么做?

  1. 识别边界:找到旧系统中可以独立替换的模块,比如用户登录、订单查询。
  2. 构建新模块:用新技术栈实现这个模块,独立部署。
  3. 路由切换:通过网关或反向代理,把对应请求转发到新模块。
  4. 逐步替换:一个模块一个模块地替换,直到旧系统只剩空壳。
  5. 下线旧系统:确认所有功能都已迁移,关掉旧系统。

关键点:绞杀者模式的核心是「增量替换」,而不是「重写」。我见过太多团队试图重写整个系统,结果两年后新系统还没上线,旧系统已经撑不住了。

下面这张图展示了绞杀者模式的核心流程:

绞杀者模式(Strangler Fig)核心流程 旧系统(遗留) 新模块1(已替换) 新模块2(已替换) 新模块3(开发中) API 网关 / 路由 逐步替换:旧系统功能 → 新模块 → 最终下线旧系统 ① 识别边界 ② 构建新模块 ③ 路由切换 ④ 逐步替换

18.5 四种策略如何选择?

你可能会问:这么多策略,我到底该用哪个?

我的建议是:不要只用一种。实际项目中,往往是组合使用。

场景 推荐策略 说明
代码耦合严重,但模块边界清晰 接缝策略 先找接缝,再逐步解耦
需要灰度验证新功能 特征开关 控制发布范围,降低风险
旧系统整体需要替换 绞杀者模式 增量替换,避免重写
以上混合场景 组合使用 接缝 + 开关 + 绞杀者,分阶段推进

避坑指南:我曾经在一个项目中,一上来就用了绞杀者模式,结果发现旧系统的模块边界根本不清楚,新模块和旧模块互相调用,搞得一团糟。后来我学乖了:先用接缝策略理清边界,再用绞杀者模式逐步替换。顺序很重要。

18.6 小结

遗留系统重构,说白了就是一场「有计划的撤退」。你不能硬来,也不能不动。接缝策略帮你找到切入点,特征开关帮你控制风险,绞杀者模式帮你逐步蚕食旧系统。

嗯,这一章的内容就到这里。记住:重构不是炫技,是解决问题。工具和方法都在这里了,剩下的就看你怎么用了。


公众号:蓝海资料掘金营,微信 deep3321