☰
别急着推倒重写:遗留系统治理与重构判断框架
2026/10/23 4:35:21 网站建设 项目流程

提到遗留系统,很多人的第一反应是“这代码太烂了,不如推倒重写”。但真正在遗留系统里摸爬滚打几年之后,你会发现一个反直觉的事实:那些看起来“蠢到不行”的老代码,在它诞生的那个年代,很可能是当时资源约束条件下最顶尖的解法。它今天被判定不适合继续演进,不是因为设计者水平不行,而是因为业务、技术栈、数据和团队全部变了,旧的那套设计逻辑已经撑不住新的局面。这篇文章想聊的,就是“到底该怎么判断一个老系统该不该退场,以及重新构建时最容易被忽视的坑”。如果你是研发、架构师或技术负责人,正在评估老系统去留,这篇文章可以给你一套判断框架。

1. 先别急着给老系统定罪:它当年很可能就是当时的最优解

1.1 老系统的“蠢”,是被时间和业务规模逼出来的

一个老系统为什么看起来越来越难用?最常见的原因不是代码写得特别脏,而是它面对的业务规模已经远超设计预期。很多系统的初始版本其实只有几十个页面、十几张表,团队两三个人,开发周期以周计。那时候根本没有条件做微服务拆分,也没有能力维护一套完整测试体系。为了快速上线,把业务规则塞进一个核心类,把参数写进配置文件,用存储过程处理复杂查询,都是非常自然的决策。用现在的标准去批评,当然浑身是问题;但放在当时的业务量、团队人数和交付压力下,它很可能就是最优解。

我印象很深的一个场景:一个电商后台的订单列表,最早只需要按用户ID查订单,因为订单量不大,根本不考虑分库分表。后来订单量上来了,查得慢,就加索引;再慢,就加缓存;再慢,就把订单表拆成热表和冷表。但整体代码结构仍然是最早那套逻辑打补丁出来的,每改一次都小心翼翼。从外面看,它确实“乱”;但从历史来看,每一步在当时都合理。这样的系统不应该急着被嘲笑,而应该被理解后治理。

1.2 判断一个老系统是否该退场,先回答三个问题

老系统要不要重写,不能靠“看到旧框架就想跑”来决定。我一般会先问三个问题:

  • 它还在承载哪些不可替代的核心流程?如果这个系统只剩一张报表有价值,那根本不需要整体重写,只要把报表对应数据源抽出来。
  • 过去一年为了加新功能,平均每改一处要动多少文件?如果每次需求都只改一两个文件,说明结构还说得过去,问题更多在外围适配。
  • 如果继续演进,瓶颈是在代码、数据、依赖还是团队认知?代码问题可以重构,数据问题要迁移,依赖问题要隔离,团队认知问题要培训或者补充新人。四种瓶颈的处理方法完全不同。

这里最忌讳的是把“代码行数多”和“用了老框架”当成重写理由。代码多不代表复杂,老框架也不等于错。真正的理由应该是“变更成本高、质量不可控、扩展受限”。如果你说不出这三个理由,就先不要立项。

1.3 老系统也可以做“外科手术式改造”

如果核心流程稳定,只是局部接口难维护,那么就不需要全量重写。可以在旧系统外围包一层接口层,新功能走新接口,旧功能继续走老逻辑;把高频变更的模块慢慢抽出来,用防腐层隔离,逐步替换。这种做法成本低,回归风险小,后期还可以随时调整节奏。

“重写”不是目的,让系统能继续支持业务演进才是目的。我见过一个运行了十多年的老后台,数据库表结构已经非常旧,但核心账务逻辑一直很稳。团队最后没有选择重写,而是在旧系统前面加了一层 API 网关,把新业务入口全部切到新服务,旧系统只保留几个核心函数继续运行。半年下来,新业务全部跑在新系统上,旧系统也没有发生重大故障。这个过程看起来不“轰轰烈烈”,但收益非常实在。

2. 技术债不是脏代码,而是业务假设失效后累积的代价

2.1 每个系统都有一组“设计假设”,假设失效就会产生债

我们评估技术债时,容易陷入“这里代码写得烂”的局部批评。但真正的问题是,每个系统在设计之初都隐含一组假设,这些假设一旦被打破,代码就会开始变得“不可理喻”。

常见假设包括:

  • 用户量级有限,所以没有做分库分表,单库查询足够。
  • 业务规则长期稳定,所以把规则硬编码进流程,不配置化。
  • 团队非常稳定,所以依赖口头约定和代码注释,不写接口文档。
  • 数据量不大,所以每次全量扫描也可以接受。
  • 下游系统不会频繁变,所以没有做重试、降级、幂等等基础能力。

这些假设在系统早期都是正确的。但业务增长后,它们逐条失效,失效的后果就沉淀成技术债。比如一个系统最开始只服务一个内部团队,所以 token 校验写在一个公共过滤器里,所有接口共用一套鉴权逻辑。后来要开放给外部合作伙伴,需要按来源、按接口、按用户维度做细粒度权限,公共过滤器就开始变成一长串 if else。表面上看起来是代码乱,本质上是“系统只服务内部用户”这个假设已经失效了。

2.2 “判死刑”的信号和判断标准

我们可以用一张表来帮助判断一个老系统是否进入“高维护成本区间”:

信号说明判断标准
新增需求周期变长一个小需求要同时改多个模块,涉及多张表平均一个新功能从启动到上线超过两周,且一半时间在处理兼容
回归测试靠人肉没有自动化测试,改A必坏B每次发版前需要手工回归的清单超过20项
环境搭建困难新同事很难快速跑起本地环境文档步骤超过30步,且仍有新人踩坑
依赖锁定严重某个旧库或中间件升级不了一次依赖升级会引发多个模块连锁报错
人才断层明显团队没人愿意维护老技术栈特定技术栈岗位连续三个月招不到合适的人

这些信号出现,说明系统进入了高维护成本区间,但不代表立刻要重写。更合理的做法是进入技术债治理周期:把核心链路和边缘功能分开,先处理风险最高、变更最频繁的部分。

2.3 治理技术债,不是把所有代码都重写一遍

我经常跟团队强调:治理技术债的选项很多,重写只是其中一种。更常见的组合拳是:

  • 给核心链路补充自动化测试,先把行为“锁住”。
  • 把高频变更的模块从旧系统里抽出来,做独立部署。
  • 对订单、账户这类强一致数据,补对账和监控。
  • 把边缘功能继续留在旧系统,先不碰它们。

用“变更频率”和“变更成本”两个维度拉一个矩阵,高频高成本的模块排最前,低频低成本的模块往后放。只要最痛的那几个点被解决,系统的整体稳定性就会明显上升。很多团队一上来就规划“三个月重写全部模块”,结果往往是一年还没上线。反而是那种“每次只替换一条链路,替换完就稳定一条”的节奏,更能落地。

3. 重写或重构前,先画清楚边界和指标

3.1 盘点现状,建立一套可度量的基线

重构开始前最怕的是“感觉系统很乱”,却说不出哪里乱。所以我建议先做现状盘点,至少包含四类信息:

  • 接口与任务清单:每个接口的调用方、调用频率、峰值耗时、失败率。
  • 数据字典:核心表、字段、主外键、脏数据比例、归档情况。
  • 部署依赖:中间件版本、外部系统、账号权限、网络策略。
  • 测试现状:哪些模块有单测,哪些模块只能靠人工验证。

这步看起来繁琐,其实两天内可以完成。你可以不用追求“完整”,但一定要可复查。后续做数据迁移、新旧对比、灰度切换的时候,没有这份基线,很多问题你根本不知道出了偏差。

我自己的习惯是每个接口补一行“最近一次调用时间”,就能快速区分哪些接口是真的核心,哪些接口已经两三年没人调用。很多时候,老系统看起来负担重,是因为大量“僵尸接口”和“僵尸任务”还在定时跑,占资源不说,还让风险面变得很大。把僵尸链路识别出来并下线,本身就是一种治理。

3.2 不要直接推倒重写,先做最小替换

我比较推荐“绞杀者模式”:新系统在旧系统旁边逐步生长,每替换一条完整业务链路,就把旧链路锁死一条。这样做的好处是每步可回滚,风险可控。具体流程可以按下面这个检查点走:

# 一条业务链路的迁移检查点 1. 预检:源数据量、目标数据量、脏数据比例 2. 双写:旧系统写成功后,异步同步到新系统 3. 校验:定时对比记录数、关键字段哈希、金额合计 4. 灰度:按用户ID、租户或订单ID分段切换 5. 回滚:切换失败后把流量重新指向旧系统

这里解释一下“异步双写”和“同步双写”的区别。同步双写会在旧接口链路里直接调用新系统,优点是实时一致,但会拖慢旧接口,而且旧系统出问题时新系统也会被连累。异步双写把同步写到队列,再由迁移任务消费,优点是解耦,缺点是会有短暂不一致。对绝大多数业务,建议先用异步双写加上定时对账;只有强一致场景才考虑同步,但同步时必须设计可靠消息和补偿。

选择第一条替换链路时,要优先选“影响面可控、数据量适中、业务容忍度较高”的链路。比如“后台导出”就比“用户下单”更适合做第一次验证。后台导出即使出了差异,影响范围有限;用户下单一旦出错,就是线上事故。

3.3 防腐层是旧系统和新系统共存的关键

新旧系统共存阶段,最容易犯的错是让新系统直接读旧系统的表。一旦旧系统改了表结构,新系统立刻跟着崩。解决办法是加一个防腐层,英文叫 Anti-Corruption Layer。

防腐层要做三件事:

  • 定义清晰的 DTO 和 API,所有跨系统调用走接口,不直接查表。
  • 新系统内部模型和旧模型完全分开,中间通过映射器转换。
  • 旧数据迁移后先清洗,再映射成新模型,不全量原样复制。

一个典型的目录结构可以是这样:

old-system/ new-system/ acl/ old-api-client/ new-api-client/ dto/

有了防腐层,两边可以独立迭代。新系统可以逐步替换旧系统,旧系统也可以在不影响新系统的情况下做一些小的维护。没有这一层,新旧系统会变成纠缠在一起的“更大的一坨”。很多重构项目越做越乱,就是因为新系统和旧系统共享了同一份数据模型,最后新系统被旧系统的历史包袱反向污染。

4. 切换流量前,先把老系统的坑摸一遍

4.1 脏数据比代码更可怕

很多系统看起来稳定,不是因为它没有脏数据,而是旧代码对脏数据做了隐式容错。新系统一旦按理想模型设计,遇到历史数据就会爆出大量异常。常见脏数据包括:

  • 必填字段为空。
  • 字段长度超过表定义。
  • 重复记录没有唯一索引。
  • 中文标点、Emoji、旧编码混在一起。
  • 时间格式不统一,有的是字符串,有的是时间戳,有的还带时区偏差。

所以在数据迁移前,要先做一轮数据分布扫描,统计每个核心字段的空值率、重复率、长度超限比例。数据清洗规则要提前和业务确认,并且保留清洗前原始表,至少保留一段时间,防止清洗规则有误导致无法回退。

我见过一个项目,迁移前看数据量只有几十万行,团队觉得很快就能搞定。结果迁移任务一跑,发现“用户手机号”字段里有大量空格、横线、+86 前缀,还有重复记录。旧系统之所以没暴露问题,是因为查询逻辑每次都会先做 trim 再比较。新系统表结构里字段做了唯一索引,数据一灌进去就报错。后来花了一周专门清洗,才把数据理干净。这个时间成本如果在项目初期就考虑进去,整个排期会合理很多。

4.2 隐式契约通常藏在注释、异常处理和硬编码里

老系统的“契约”不只在接口文档里,更多藏在代码细节中。我整理过一份最容易漏掉的地方:

隐藏位置常见问题
字段名拼写

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询