让 AI 读懂你三十万行的老项目:遗留代码迁移的四个阶段

📅 2026/4/26 ✍️ 小文 📖 约 1 分钟

把整个仓库丢给 AI 是一次昂贵的赌博。本文给出一套分阶段迁移方法:先建地图、再冻接口、然后逐个迁移并在每一步跑回归、最后处理跨模块语义漂移。附提示词模板与验收标准。

遗留代码迁移是 AI 编程最被低估的战场:需求明确、有大量参考实现、失败可以被测试捕捉,理论上非常适合自动化。但实际做过的团队都知道,把整个老仓库丢给 AI 让它”重构一下”,结果通常是一份看起来能跑、实际到处是隐性行为改变的代码,而且没人敢合并。

问题不在模型能力,在方法。遗留系统的难点从来不是写代码,而是弄清哪些行为是不能变的。下面这套四阶段方法,是我们用来把一个十二年的 Java 单体迁到模块化服务的实践。

第一阶段:让 AI 画地图,而不是改代码

第一步什么都别改。用 AI 做的第一件事是理解。

具体做法:按目录分批把代码喂给模型(每次 10-20 个文件),让它输出结构化描述:

对以下文件,输出:
1. 每个类/函数的职责(一句话)
2. 外部依赖(数据库表、HTTP 接口、消息队列、定时任务)
3. 被谁调用(从 import 和引用反查)
4. 副作用(写库、发消息、写文件、改全局状态)
5. 你认为的隐性耦合(共享可变状态、隐式的执行顺序依赖)

重点在第 4、5 项。迁移里 80% 的事故来自副作用和隐式顺序依赖,而这些在 IDE 的”查找引用”里是看不见的。

把每批输出汇总成一份 map.md。它有两个用途:一是给人看,二是之后每一次让 AI 改代码时,把相关的 map 片段作为上下文一起给——这比让它重新读一遍源码便宜且准确。

第二阶段:冻结接口,先写契约测试

有了地图之后,不要急着动核心逻辑。先做两件事:

冻结对外接口。 把现有 API 的请求/响应样本收集起来,录制 200-500 条真实流量的输入输出对。这是后续所有验证的黄金基准。

补契约测试。 让 AI 基于这些录制样本生成测试用例,只断言对外可见的行为,不关心内部实现。这是关键设计:迁移允许实现完全改变,但不允许外部行为改变。

提示词模板:

基于以下请求/响应样本,生成契约测试。要求:
- 只断言状态码、响应体关键字段、副作用(写入了什么)
- 不断言日志、不断言内部方法调用
- 对每个样本,标注你识别出的隐式约定(字段顺序、空值处理、错误码含义)
- 如果样本之间有矛盾,列出来而不是猜一个

“列出矛盾而不是猜”这句很重要。老系统里经常有互相矛盾的约定,那往往就是埋在暗处的 bug。

第三阶段:按切片迁移,每一步都要能回滚

按”垂直切片”迁移,而不是按层。选一个完整的小业务路径(比如”创建订单”这一条链),从头到尾迁完、跑通,再上一个。不要横着来——先把所有 DAO 换一遍,会得到一个既不能跑也没法回滚的中间状态。

每个切片的流程固定:

  1. 让 AI 阅读老切片 + map + 契约测试,产出新实现与一份差异说明:哪些行为保留、哪些刻意改变、为什么。
  2. 人工重点审差异说明,而不是逐行审代码。逐行审三万行不现实,审 30 条差异说明可行。
  3. 跑契约测试,再跑该路径的端到端测试。
  4. 灰度:新老实现并行跑,对比输出,只对老路径返回。跑够一周无差异再切。

第 1 步的”差异说明”是这套方法的核心产出物。它把”AI 到底做了什么”从不可审计变成可审计。

第四阶段:处理语义漂移

最后一个阶段最容易翻车:单看每个切片都对,整体行为变了。典型症状是跨模块的枚举值含义被重新解释、日期时区处理不一致、错误码在链路中被改写。

对策是跨切片的语义词典。在 map 阶段就建立一个 glossary.md:每个领域术语、枚举值、状态码的确切含义。每次让 AI 生成代码时,把词典强制注入上下文,并在提示词里写死:“不得新增枚举值,不得改变已有值的含义;如需新增,先列出理由,不得直接实施。”

迁移完成前做一次全链路对账:用生产流量重放,对比最终写入数据库的差异。这一层检查比任何单元测试都值钱。

什么时候不要用 AI 做迁移

三种情况建议人来做:核心算法只有原作者理解且没有测试覆盖、涉及强一致性事务边界、以及代码里存在大量依赖运行时反射或动态派发的间接调用。这些地方 AI 会基于”看起来对的模式”生成代码,而错误极难通过测试暴露。

一句总结:AI 擅长迁移已经理解的东西,不擅长发现你还没理解的东西。 先把理解补齐,再谈自动化,顺序对了,效率才是真的。

📤 分享到