☰
用Claude Code重构遗留系统:从行为快照到灰度发布的全流程实践
2026/10/21 16:24:07 网站建设 项目流程

过去大半年,我们团队一直在推进一件听起来不算新潮、做起来却异常折腾的事:把一套运行了近十年的订单审批系统,从巨石单体逐步拆改到新的服务架构。这个过程中,Claude Code 从一个不起眼的命令行工具,变成了整个重构项目的关键杠杆。这篇文章想把完整的方法链写下来,从“这系统到底还值不值得救”的前置判断,到“AI 改过的代码怎么验证”的收尾动作,全流程复盘一遍。内容比较长,我按实际推进的顺序分章节写,适合正在评估 AI 工具做技术债清理的团队参考。

1. 重构动身之前,先逼自己回答清楚三个问题

1.1 遗留系统的“可救性”判定:不是所有老代码都值得重构

我见过太多团队把“重构”做得像一场慈善:代码烂,想重写;文档没,想重写;换人交接不顺,也想重写。但 Claude Code 这类工具不改变事情的本质——它只是把“改代码”的杠杆放大,如果系统本身没有重构价值,AI 只会帮你更快地造出一堆新债。

做可救性判断,我一般只看几个硬指标:

  • 业务是否还在持续迭代:如果这个系统未来两年不会有新需求,只有零散的修修补补,那我建议别碰它,维持现状就是最低成本。
  • 变更频率是否已经高到难以承受:比如一次小需求要改五六个模块、回归测试要跑三天,这种系统才有重构的急迫性。
  • 故障与维护成本是否在侵蚀利润:线上问题定位慢、环境部署难、新人上手期长达数月,这些隐性成本比代码本身的丑陋更致命。
  • 是否有足够的回归保障:没有测试基线就重构,等于没有安全网走钢丝。后面我会详细讲行为快照怎么搭。

如果四个指标只占一两个,先别急着上 AI。重构的收益不是“代码变漂亮”,而是“未来的每一次修改都更快、更安全”。

1.2 Claude Code 在重构里的真实定位:它是杠杆,不是替身

很多人第一次接触 Claude Code 时,会把它理解成“一个能自己写代码的机器人”,然后幻想给它一句“把这个系统重构了”就能等结果。这个幻想会在第一次任务后迅速破灭。

Claude Code 在终端里以命令行智能体的形态工作,它真正擅长的事情是:在一个比较大的代码库里做跨文件的理解、检索、修改和验证。它可以把“通读一遍模块代码 + 列出所有依赖关系 + 找出隐藏约定”这种过去需要一位高级工程师花两三天才能做完的代码考古工作,压缩到几十分钟。这比它直接替你写出多少个函数都有价值。

我用一个类比来形容它:它像一个记忆能力极强但缺乏行业经验的实习生。你对它说清楚背景、约束、边界,它能在很短时间内把活干完;但它不会主动替你做技术决策,也天然不知道你们团队那些写在口头上的隐性规则。所以整个重构流程的设计,必须围绕“给它足够清晰的上下文,然后人工守住质量闸门”来展开。

1.3 重构方式选型:整体重写、绞杀者模式,还是 AI 辅助的模块替换

重构遗留系统的方案无外乎三种,但搭配 AI 工具后性价比完全不同。

  • 整体重写:风险最高的一种。旧的没了、新的没跑通时,业务就停摆了。即便有 Claude Code 辅助,我也不推荐,除非系统短小到可以在一两周内重建。
  • 绞杀者模式(Strangler Pattern):把老系统按模块逐步替换到新架构,新旧系统并行运行,每替换掉一块就从老系统里切走一块流量。这是我认为最适合配合 AI 工具的方式,因为每个模块都可以独立准备上下文、独立验证、独立上线。
  • 并行替换:用新系统完整实现了业务后才切换流量,适合业务逻辑相对固定、交互边界清晰的系统,但工期通常比绞杀者模式长。

在绞杀者模式下,Claude Code 的价值能最大化发挥:每个被替换的模块是一个相对独立的“工作包”,上下文可控、验证标准明确、回滚范围有限。下面的整套工作流,都是基于这个选型展开的。

2. 落地前的工程准备:不搭好这两层地基,AI 越强越危险

2.1 行为基线:没有安全网的代码考古就是高空装睡

我在重构前做的第一件事,不是写新代码,而是给老系统建立一套“行为快照”。

所谓行为快照,简单说就是把老系统所有核心逻辑的输入与输出先固化下来,作为后续验证基准。最常用的方式是Golden Master Test(黄金主测试):构造一批覆盖典型业务路径的测试数据,跑老系统,把结果(响应内容、数据库变更、日志序列)全部记录下来。之后 Claude Code 改完的代码,跑同一批数据,逐字对比结果。

这一步有什么价值?它的价值在于:过了十年、换了好几轮人之后,没有任何人敢说自己完全清楚老系统的全部行为了。但测试数据不会骗人。行为快照不是“测一下功能正不正常”,而是“把老系统的行为模式完整拍照”,之后 AI 的任何改动都会被放到这个照片面前比照。

实际操作上有两个细节需要注意:

  • 数据覆盖度要够。别只挑“正常流程”的数据,边界值、异常值、历史脏数据都要塞进去。老系统最大的坑往往不在正常路径上,而在那些早已没人记得的特殊分支里。
  • 快照要独立于环境。把快照数据和执行脚本放到独立目录,不依赖老系统的运行环境,否则重构过程中老系统一关机,基线就废了。

2.2 项目手册与上下文注入:Claude Code 的行动边界清单

Claude Code 在工作时需要读取项目上下文。项目根目录下放一份 CLAUDE.md 文件,是这套工作流里最基础也最关键的一步。它相当于一份给 AI 的“入职手册”,每次对话开始前被加载,用来约束行为边界。

我实际使用的 CLAUDE.md 包含以下内容:

## 项目概述 - 这是一个订单审批系统,采用 XX 架构,包含服务端、调度任务、消息消费三部分 - 重构目标是逐步迁移到新服务,保持外部接口不变 ## 代码结构 - /legacy 存放旧代码,重构期间不要直接修改内部实现 - /new-service 存放新代码,所有新增逻辑写在这里 - /tests/golden 存放行为快照测试基线 ## 变更规则 - 禁止直接修改数据库、生产环境配置 - 禁止使用危险命令删除文件或批量替换全局变量 - 新代码必须通过全部测试后才可提交合并请求 - 遇到不确定的业务逻辑时,不要猜测,把问题写入 REFACTOR_NOTES.md ## 常用命令 - 运行测试:npm test - 运行快照对比:npm run golden - 格式化:npm run format

注意几个细节:“不确定时不要猜”这条规则特别重要。遗留代码里经常会出现看起来是冗余、实则是关键补偿逻辑的代码,AI 在优化时会倾向于“删除多余代码”,结果把补偿逻辑删了。有了这一条约束,它会停下来问,而不是自作主张。

同样重要的还有“重构期间不要直接修改旧代码内部实现”这一条。模块替换过程中新旧系统要并行运行一段时间,如果 AI 一边写新模块一边顺手改了旧模块,两边行为就会漂移,出了线上问题连比对基线都找不准。

2.3 权限边界与观察窗口:先让它半自主跑一段

在 CI/CD 权限、终端权限这件事上,我的原则是:第一周先不给写权限,只给读和检索权限。让 Claude Code 在完整代码库上做模块梳理、依赖分析、输出报告,人工检查报告质量,确认它真的懂这个系统的结构后,再逐步放开到测试分支的写权限。

原因很简单:你没法从一个工具的说明书里判断它是否理解你的系统,只能从它的工作输出里判断。先让它跑一段只读任务,是成本最低的信任测试。

成本控制上,建议在初始阶段设置预算上限。Claude Code 这种工具在重构场景下是会持续调用模型的,一旦你把一个大模块丢给它,它会反复读取文件、执行命令、运行测试。没有预算上限的话,一个周末就能烧掉一个不小数额的 API 费用。我们当时的做法是按模块设置成本额度,超出后切换成手动批准模式,所有后续动作都由人逐条确认。

2.4 任务包拆分:把重构拆成 AI 能独立完成的“微项目”

给 Claude Code 一个大任务,它很容易做到一半上下文超限,然后开始犯低级错误。所以任务粒度要足够小。

我用的拆分模板是这样的:

## 目标 将订单审批模块中的「审批流引擎」从旧服务迁移到新服务,保持对外接口不变。 ## 输入范围 - 旧代码路径:/legacy/approval-engine/** - 依赖清单:/tests/fixtures/dependency-analysis.md ## 输出交付物 - /new-service/approval-engine/** 下的完整实现 - 行为快照测试通过 - 依赖分析差异报告 ## 成功标准 - golden test 全部通过 - 接口响应字段与旧系统 100% 一致 - 错误处理分支覆盖率达到旧系统同等水平

这个模板有几个意义:给 AI 明确的边界(不会乱改到范围外)、明确的验证标准(做完后自己能判断成功与否)、明确的交付物(人工检查时有清晰对象)。

一个模块的任务周期我控制在 2 到 4 小时以内。超过这个时间就开始拆分,拆不动的往往说明依赖没理清,这时候停下来做依赖分析比硬推进有价值得多。

3. 实战流程:让 AI 真正动手前,人要先想清楚“做什么”

3.1 第一步:入口到出口的依赖图谱梳理

推进一个具体模块迁移时,我给 Claude Code 的第一个指令永远是“梳理,不是修改”。

一个典型指令是这样的:

通读 /legacy/approval-engine 模块下所有代码,输出: 1. 模块所有对外入口(HTTP 接口、消息消费者、定时任务) 2. 模块依赖的外部系统(数据库表、缓存、消息队列、第三方服务) 3. 模块内部的隐藏状态和全局变量清单 4. 所有错误处理分支的位置与触发条件 把结果写到 /docs/dependency-analysis.md。不要直接修改任何代码。

为什么先做这一步?因为遗留系统最危险的地方就是“图上没有的连线”。接口文档可能写着只依赖一张表,实际代码里还动了缓存、发了消息、调了外部服务。这些隐藏依赖不摸清,迁移过程中必然爆雷。

Claude Code 在长上下文理解上的能力,在这里体现得最充分。它能把几十个文件的调用关系读完后整理出一份结构化清单,人工只需要做抽查复核。以往这份工作要花两三天,现在压缩到一个上午。

这份依赖清单有两个关键用处:

  • 决定新模块的服务边界。哪些属于模块内部,哪些必须走接口调用,都在清单里定清楚。
  • 决定迁移的先后顺序。依赖最少、边界最清晰的部分先迁,复杂依赖留在后面,降低每次切换的风险。

3.2 第二步:让 AI 先写方案,再写代码

依赖清单出来后,我依然不让它直接改代码,而是先让它产出一份“迁移设计方案”。这个步骤很容易被跳过,但它非常重要。

方案里必须包含:

  • 新旧接口的映射表(旧接口的每个字段对应新系统哪个字段)
  • 数据迁移方案(旧表的数据是否需要同步,如何同步)
  • 错误处理策略(旧逻辑里每个 catch 分支,新代码必须如何对应)
  • 兼容层设计(哪些接口需要临时兼容,保证灰度期间新旧系统同时可用)

有个反直觉的经验:给 AI 设计方案的产出质量很大程度上取决于你给它多少“反面约束”。只写“请输出迁移方案”是不够的,要同时给它旧代码的种种怪癖。比如“注意 getStatus() 方法在逾期场景下把状态码溢出成负数,你们在旧代码里是靠这条判断强制归档的”——这类隐性约定如果不从人的角度补充,AI 设计方案时很可能把负数判断当 bug“修复”掉,再跑正常测试也发现不了,因为它不会构造逾期数据。

所以每次开始方案设计前,我会把团队里老成员能回忆出的所有“旧系统怪癖”先记下来,一并放进上下文中。这是纯人工输入,也是整个流程里最不可替代的部分。

3.3 第三步:兼容层与灰度切换,让系统全程可运行

真正写代码的阶段,我按照“新代码 + 兼容层 + 灰度开关”三件套来做。新代码实现完整业务逻辑,兼容层保证灰度期间老系统和新系统都能处理同一批流量,灰度开关决定哪些请求走新链路、哪些请求走旧链路。

这个阶段 Claude Code 的任务通常是这样的:

在新服务下实现审批流引擎,要求: 1. 与旧系统实现相同的外部行为 2. 通过灰度开关控制链路切换 3. 在审批流关键步骤输出结构化日志,便于后续行为比对 4. 不得改动旧系统的任何逻辑

灰度切换前,我已经把行为快照测试跑通了,但快照是离线验证,线上的复杂流量还可能有盲区。所以灰度期间我保留了完整的日志链路,线上真实请求同时打到新旧系统,对两边产出的状态、结果做实时比对。比对一致后,再把灰度比例从 5% 逐步提高到 100%。

这一步有个容易犯的错:很多人切流量只对比成功场景。实际上一旦把异常流量也切过去,新旧系统的差异会立刻暴露出来。所以灰度比对时,要把“成功、驳回、异常、超时”四类结果都比对一遍,差异为零才继续放量。

3.4 人工审查 AI 代码时的三个重点位置

新代码跑通测试不代表可以放手。Claude Code 产生的代码风格通常很工整,但恰恰是这种工整会掩盖问题。我做代码审查时会重点看三个位置:

第一,事务边界。遗留系统里很多操作依赖外层事务的隐式传播,AI 在重构时为了“代码整洁”,经常给内部方法加上新事务,导致原本一次性提交的逻辑被拆成多次提交,中途失败时数据就处于不一致状态。凡是涉及多表更新的地方,逐个确认事务边界与原行为一致。

第二,隐藏状态与全局变量。这是遗留系统里最容易被 AI 理解错的东西。有些模块依赖一个在启动时候加载的内存状态,AI 把它“优化”成每次实时查询之后,行为对不上且性能下降。审查时专门搜索 new 代码里的静态变量和全局缓存定义,逐个问“这个状态从哪里初始化,生命周期是否与旧系统一致”。

第三,错误处理分支。正常路径 AI 写得很好,但错误处理分支经常出问题。比如旧代码在某个重试失败后会放弃并告警,AI 重构后可能把重试逻辑写成无限重试,或者把告警吞掉。这个不细看代码,靠测试很难发现。

4. 质量守门员:一个重构模块上线前必须过完的关卡

4.1 行为快照测试:离线阶段的绝对判官

前面提到的 Golden Master 测试,在每一个模块迁移完成后都要重新跑一遍。跑不过的直接打回,没有例外。跑过了也不等于万事大吉——快照数据本身的质量决定它的杀伤力。

我会定期做一次“快照反向审查”:随机看几个所有路径都通过的测试用例,人工问一句“这个输入真的覆盖了真实业务中可能出现的场景吗”。如果只覆盖了常规流程,就要继续补边界用例进去。测试的好不好比测试的多不多更重要。

4.2 对比式验证:把新旧系统放在镜子前

离线测试过了,灰度前的最后一步是对比式验证。我会把同一份最近几周的生产流量样本同时喂给新旧两个系统,逐字段比对输出结果。这里强烈推荐把两边的输出都转成稳定的文本格式再做 diff,不要让比对过程本身依赖复杂环境。

以下是我常用的比对维度表:

比对项具体内容一致性标准
状态码与状态机每个审批节点的流转结果完全一致
数据落库数据库字段变更前后逐字段一致
对外消息消息队列发送的内容与顺序完全一致
异常行为超时、重试、降级策略行为一致(不要求文案一致)
处理耗时同量级数据下的响应时间新系统不得慢于旧系统 30% 以上

4.3 性能与资源复查清单

很多遗留系统的重构都是“重构前不觉得慢,重构后发现慢十倍”,主要原因是新技术栈引入的资源开销没有提前评估。Claude Code 是辅助工具,但它写出来的新代码如果跑在云环境上,数据库连接数、内存占用、冷启动时长这些指标,它自己不会主动关注。

上线前的性能复查清单我固定在五个项目:

  • 数据库连接池大小是否与旧系统匹配。新服务如果开了多个实例,连接池要相应调整,否则会打爆数据库连接上限。
  • 缓存命中率是否有变化。新代码对数据的读取模式如果变了,可能让缓存从“每请求命中”变成“每次都要回源”。
  • 慢查询是否新增。AI 生成的数据访问代码,如果不注意索引选择,全表查询的隐患很大。
  • 内存占用与 GC 表现。跑一小时的压测,观察是否有内存泄漏趋势。
  • 冷启动耗时。如果是容器化部署,新服务的启动时间不能拖垮滚动发布。

4.4 灰度放量与回滚预案

灰度放量的比例我习惯按 1%、5%、20%、50%、100% 五档走。每一档停留时间至少观察一个完整业务周期,确认监控无异常后再进下一档。这里说的监控不是只看成功率和响应时间,还要看业务结果指标——比如审批通过率、平均处理时长这些,如果这些指标在灰度后出现偏移,很可能就是新代码在某个分支上的行为差异。

回滚预案必须提前写好。我的最低标准是:一键切回旧系统不超过五分钟,并且回滚不丢数据。凡是新系统写了存量数据、还没做反向同步的模块,一律不允许进入高比例灰度,因为一旦出问题,数据已经被改掉了,回滚也回不到从前。

5. 踩坑实录:这类工具在重构场景里容易翻车的七个位置

5.1 上下文超限后开始“自信地编造代码”

做重构的时候最典型的翻车场景:一个模块太大,对话历史太长,Claude Code 开始忘记早前提到的约束,然后自信地补一段不存在的接口或变量。它编造出来的代码看起来完全合理,编译也能过,但运行时才发现调用的方法根本不存在。

我的应对方式前面提过:任务必须足够小。除此之外,在 CLAUDE.md 里明确写了“如果某个依赖的接口不确定,必须明确说明而不是自动猜测”——加上这条之后,编造率明显下降。人机协作的边界要提前划在规则里,不能靠临场发现。

5.2 老系统依赖清单漏项,AI 拆出个“伪模块”

依赖分析做得不充分就容易出这种事:AI 认为某个模块只依赖两张数据表,迁移完毕后灰度一开,才发现它其实还消费了一组消息队列里的消息、触发了一个隐藏的定时任务。表面上是 AI 拆错了,实际上是我们的依赖清单没有覆盖完整。

避免的方法不太复杂,却很容易偷懒:依赖图谱一定要有第三方佐证,两个视角交叉验证。AI 读代码写一份依赖清单,运维同学从数据库慢查询记录、消息队列日志、系统调用链逆向核对一遍。两边差异一致,才算真正摸清了边界。

5.3 危险命令权限失控

有一段时间我为了效率,把工具调成了自动批准模式。结果它在一个“清理临时文件”的任务里,把包含测试历史快照的目录按模式匹配删掉了。幸好当时所有基线都提交到了版本库,但这种事发生一次就足够让人后背发凉。

之后我定了规矩:写操作一律手动确认,删除操作要二次确认,生产环境相关的任何操作都必须走人工程序。效率损失一点没关系,数据没了才是灾难。

5.4 测试覆盖率数字好看,实际行为没被保护

AI 写代码有一个特点:它写测试的时候会“顺势而为”——和它写的实现共享同一套错误理解。如果它把某处业务逻辑理解错了,它的测试也会按同样的错误来验证,测试全绿,逻辑照样是错的。

这就是为什么要用行为快照作为独立的第三方判官。AI 自己写的测试只能算是辅助,行为快照是重构前就固化下来的,和 AI 的实现没有任何共谋关系,所以它是唯一可信的验收标准。

5.5 依赖升级的连锁反应

Claude Code 在生成新代码时,有时会顺手建议升级依赖库版本。表面上这只是“解决编译问题”的动作,但实际上一个主要版本升级可能带来行为上的细微变化。我们在一个模块里遇到过多线程调度行为变化导致资源竞争的情况,排查了很久。

从那以后,我要求它所有依赖版本变更必须写入变更说明,并且在人工审查时单独过一遍依赖变化。

5.6 期望它“顺便理清业务需求”,结果越帮越忙

ChatGPT 这类工具在“解释业务逻辑”上表现很好,Claude Code 也一样。但这也导致一个隐患:团队遇到需求不清晰时,会指望 AI 把这段逻辑的前因后果理清楚。实际上,遗留代码里大量逻辑是为曾经的、可能早已不存在的业务规则服务的。AI 可以告诉你代码“是怎么写的”,但无法告诉你“当初为什么这么写”。

那些“为什么”只能从老团队成员、旧需求文档、很久以前的运维故障记录里找。把 AI 当需求考古工具、而不建立人的访谈机制,是整个流程里最大的认知误区。

5.7 代码风格漂移,新的代码库出现“双重人格”

如果没有在 CLAUDE.md 里写好代码规范,AI 生成的代码会带有它自己的风格惯性,和团队既有风格不一致。单个文件看不出问题,几十个文件合并后,新模块和旧模块的代码风格明显割裂,后续维护的人会非常难受。

解决办法是在项目手册里放一份简化版代码规范,并在审查阶段加入“风格一致性”检查项。这项工作机械但对长期维护价值很大。

6. 从一次完整重构中沉淀出的经验,和再出发时要调整的事

整个流程从评估到最终把一个核心模块完整迁出,我们用了一个多时间。在这个周期里,最初两周几乎全部花在行为快照和依赖梳理上;中间三周是最痛苦的阶段,兼容层反复调整,灰度比对暴露了十几个行为差异;最后的收尾阶段反而平稳,因为验证机制成熟后,每一次提交的风险都在可控范围内。

人力投入很多人会低估。表面上是“AI 写代码”,实际上重构过程中的评审、测试数据构造、灰度运维、需求澄清,每一个环节都有人必须全程在场。统计下来,真正省下的是初级工程师做代码考古的时间,而资深工程师的精力反而更聚焦在更重要的问题上:边界怎么切、兼容怎么做、风险怎么控。

项目结束后我们做了一次团队复盘,有三条结论被写进了后续的协作规范:

  • 任何 AI 代码提交必须附带一句“行为变更说明”,没有说明的直接打回。
  • 每个模块的 CLAUDE.md 上下文要在任务开始前更新完成,任务过程中不再追加隐性约束。
  • 灰度窗口期必须有专人盯业务指标,不允许只看技术指标。

复盘还有一个容易被忽视的收获:长期维护这个老系统的同事通过这次重构,终于把那些“藏在心里的逻辑”完整表述出来了,并且沉淀成了文档。这某种意义上比代码迁移本身更有价值——隐性知识显性化之后,团队对这套系统的理解才算真正换代。

如果要按照这次经验再重做一次,我会在前期把更多时间留给业务方访谈,让所有历史业务规则在动手前就形成书面材料。工具能力再强,也替代不了对系统“为什么长成现在这个样子”的理解。重构的本质不是消灭旧代码,而是为未来的演进修一条更平坦的路。

我个人最后想分享的一个实践技巧,也是我每次任务收尾时固定会做的一件事:让 Claude Code 在提交前先输出一份简洁的变更摘要——改了哪些文件、为什么改、风险点在哪里。这份摘要一方面节省了 Code Review 的时间,另一方面让我迅速发现哪些改动是意料之外的。意外改动越少,说明上下文给得越准。如果你把摘要里频繁出现意外改动当作一个信号,顺着它去改进任务描述的质量,你的团队用这类工具的效果会越来越稳。

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

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

立即咨询