返回全部文章

重构存量系统:不要停下来换引擎

本文目录

工程师很容易被“重写”吸引。

旧代码缺少边界,页面依赖 jQuery,接口返回不一致,改一个字段会影响三个系统。面对这样的代码库,重新选一套技术、从空目录开始,感觉既干净又高效。

但长期运行的业务系统通常不能停下来等你换引擎。它每天有预约、支付、报告和运营人员在使用,重构必须与需求交付同时发生。

我曾参与一个长期运行的医疗预约产品线:后端持续使用 Laravel,诊所后台从 jQuery 逐步迁移到 Vue 2,后来再演进到 Nuxt 2。这个过程让我确认了一件事:存量系统重构的核心,不是替换技术,而是逐步重建边界。

先理解系统为什么变成现在这样

旧代码并不一定来自糟糕的工程能力。它往往是多年的业务压力、人员变化和当时技术条件共同留下的结果。

在修改之前,我会先画出四类关系:

  • 哪些模块直接创造业务价值;
  • 哪些数据被多个系统共同读写;
  • 哪些流程与外部平台强绑定;
  • 哪些代码虽然难看,却已经稳定运行多年。

最后一类尤其重要。重构不是以“代码是否优雅”排序,而应该综合考虑变更频率、故障影响和维护成本。

一个半年不会变化的旧页面,即使内部不够漂亮,也可能不值得优先处理。相反,一段每周都要修改、每次都容易出错的订单逻辑,才是应该先建立边界的地方。

不要同时改变所有变量

一次大重写通常同时改变语言、框架、数据模型、接口和部署方式。任何问题出现时,团队很难判断究竟是哪一层造成的。

渐进式重构要刻意控制变量。

以页面迁移为例,可以先保持后端接口和部署不变,只把一个高频模块从 jQuery 改为 Vue;等组件结构与状态管理稳定后,再统一接口;最后才考虑路由和服务端渲染框架。

我常用的顺序是:

  1. 先包住变化:为最混乱的外部调用加适配层;
  2. 再明确契约:稳定请求参数、响应结构和错误语义;
  3. 然后迁移页面:新需求只写在新结构中,旧页面按价值逐步替换;
  4. 最后清理旧入口:确认流量和功能都已迁移,再删除旧实现。

这不是最快的写法,但它让每一步都可以验证、回退,也让业务持续运行。

用新需求支付重构成本

单独申请几个月“只做重构”,在大多数业务团队里并不现实。更可行的方式,是把重构和真实需求绑定。

如果下一个需求要改预约表单,就顺便把表单校验从散落的 DOM 操作收进组件;如果要增加新的报告类型,就先抽出报告生成的任务接口;如果要接入一个新客户端,就借此统一已有 API 的认证和错误码。

这个策略有三个好处:

  • 每次重构都有明确业务收益;
  • 新边界会立即接受真实场景检验;
  • 团队不需要维护一套“重构中的理想系统”和一套“仍在赚钱的旧系统”。

当然,不能借每个小需求无限扩大范围。一次迭代只改善与当前变化直接相关的边界,其余问题记录下来,等待合适时机。

数据库是最后需要冲动的地方

前端页面可以逐页替换,数据库结构却连接着报表、脚本、异步任务和多个客户端。一张看起来命名不合理的表,背后可能有很多隐形依赖。

对于数据层,我更倾向于:

  • 先增加新字段,而不是立刻修改旧字段语义;
  • 使用迁移脚本补齐历史数据;
  • 在一段时间内同时读写新旧结构;
  • 用校验任务比较两套数据结果;
  • 确认所有消费者迁移后,再停止旧结构写入。

这是一种“扩展—迁移—收缩”的过程。它会产生短期重复,但把高风险的一次切换拆成了多个可观察步骤。

接口是最值得优先稳定的边界

当系统拥有 Web、管理后台和移动端时,接口质量会直接决定重构难度。

一个稳定的接口边界至少应该回答:

  • 字段是否有明确含义和单位;
  • 缺失、空值与零是否能区分;
  • 错误是否有机器可读的代码;
  • 客户端是否需要理解数据库实现;
  • 新字段加入时是否破坏旧客户端。

我不追求一开始就设计出永远不变的接口,而是让变化显式发生。必要时保持版本兼容,或者由服务端提供过渡字段,而不是要求所有客户端在同一天更新。

存量系统里,好的架构不是从未改变,而是改变时不会把所有人绑在同一个发布窗口里。

每一步都要能证明更好了

“代码看起来更现代”不是充分的重构结果。每个阶段都需要可验证信号,例如:

  • 同类线上故障是否减少;
  • 新功能需要修改的文件是否变少;
  • 页面加载和交互性能是否改善;
  • 新成员理解模块需要多久;
  • 发布是否更小、更频繁、更容易回退。

自动化测试当然重要,但存量系统很难一开始就拥有完整覆盖。可以先保护最关键的业务规则,再增加接口契约测试、迁移校验和少量端到端流程。

测试也应该跟着边界一起生长。

接受重构没有“完成”的一天

长期产品不会在某一天宣布“技术债已经清零”。业务会继续变化,今天的新结构也可能成为未来的旧系统。

比彻底重写更重要的,是让团队形成持续改善的节奏:新代码遵循新的边界,高频旧模块随需求迁移,低价值区域保持稳定,对危险依赖保留清晰记录。

重构的目的不是获得一份完美代码,而是恢复团队安全改变系统的能力。

就像一辆仍在行驶的车,我们不需要停车换掉所有零件。我们需要知道先换哪一个、如何确认它工作,以及出现问题时怎样回到上一站。

LET’S BUILD SOMETHING THAT LASTS

持续构建,保持好奇。

如果你也在构建需要长期运行的产品,欢迎聊聊。