接手过几个老项目的朋友应该都有这种体会:代码能跑,但没人敢动;文档缺失,核心逻辑只存在于几个老同事的脑子里;技术栈停更了好几年,想招人都难。这种系统被叫做“遗留系统”不是没有原因的,它就像一栋住了几十年的老房子,水电管线全埋在墙里,谁都不知道改一处会不会引发别处漏水。最近我一直在用 Claude Code 这类 AI 编码代理工具来处理这类改造,说实话,效果比我预想的要好不少。
Claude Code 本质上是一个跑在终端里的 AI 助手,它能直接读取你仓库里的文件、执行命令、改代码、跑测试,像一个“长在项目里的结对工程师”。用它的视角去拆解遗留系统的重构,整个路径会清晰很多:从最初的代码库体检、可行性判断,到分阶段迁移、灰度验证,再到各种翻车现场的排查,这套流程我已经跑了好几遍。这篇文章就把我实际踩过的坑、验证过的方法、以及一些不太会写进文档里的细节,从头到尾整理出来。适合正打算对老系统动手的技术负责人、后端开发,也包括那些刚接手旧项目不知道从哪里下手的同学。
1. 遗留系统的典型困局与重构切入点
1.1 遗留系统为什么越来越难动
很多人以为“遗留系统”就是指技术老旧的系统,其实不完全是。我见过用着很新框架但代码乱成一锅粥的项目,也见过 COBOL 风格的老古董却在关键业务里稳如磐石。真正让一个系统进入“遗留状态”的,往往是下面几个特征的组合。
第一是知识断层。写这套系统的那批人可能已经离职了,留下来的文档要么缺失,要么和代码对不上。最典型的情况是:你问老员工“这个状态字段总共有几种取值”,他打开代码翻了一会儿,回答“你搜搜常量文件吧,应该在那边”。这种“知识藏在代码里、代码里全是坑”的状态,是重构最大的隐性成本。
第二是结构性腐化。早期为了快速上线,大家约定俗成地绕开了规范:一个 Service 类几千行,全局变量到处飞,SQL 直接拼字符串,定时任务和业务逻辑混在一起。随着功能迭代,模块之间的依赖关系越来越乱,连画出完整调用链都变得不现实。
第三是自动化测试缺失。没有测试,就意味着任何一次重构都无法快速验证“我没改坏”。但你没法怪当初的人,那个年代写测试的投入产出比确实不高,很多团队到后期甚至默认“老模块不动就是胜利”。
这三个问题叠加在一起,导致绝大多数团队对遗留系统的态度是“能加功能就加功能,实在不行就新开一套项目”。全量重写的坑更大:业务逻辑的复杂性远超你从旧代码里看到的表面,重写过程中丢失的那些边界情况,足够让你上线后连续加班两个月。所以真正可行的路径,是借助 AI 工具把重构变成一项“可评估、可拆分、可验证”的工程,而不是一场赌博。
1.2 AI 辅助重构与传统人工重构的核心差异
传统人工重构最大的问题不是写代码的能力,而是心理负担。面对一个几百万行的老系统,你连“从哪开始读”都不知道。我见过最典型的情况:一整个小组调研了两个月,PPT 做了几十页,最后结论是“建议分期重构”,但第一期做什么、怎么保障切换过程不炸,谁也说不清。
用 Claude Code 这类工具,事情开始变得不一样。它最大的价值在于把“探索代码库”这件事的边际成本拉到了极低。你直接让它去读某个目录、追踪某个字段的流转、列出某个接口的所有调用方,它几分钟就能给出一份带文件路径和行号的报告。换成人来做,至少半天起步。
但这不意味着 AI 可以替代架构判断。Claude Code 更像一个执行力极强的助手,你给它方向,它负责把细碎功夫做扎实。比如你要了解支付模块的完整流程,它能帮你遍历所有分支;但“支付模块是否值得重构、重构后应该采用什么领域模型”这类决策,还是需要人来拍板。
我整理过一个对比表,可以很直观地看到差异:
| 维度 | 传统人工方式 | Claude Code 辅助方式 |
|---|---|---|
| 代码理解速度 | 依赖人工阅读,一个模块可能几天 | 分钟级输出结构分析和调用链梳理 |
| 重构方案设计 | 靠专家经验,容易遗漏边界 | 可以快速枚举多个方案并对比差异 |
| 机械性改造(重命名、拆函数、改调用点) | 费时费力,容易手误 | 自动批量处理,速度快且一致性高 |
| 回归验证 | 依赖完整测试体系,老项目往往缺失 | 可以生成针对性测试,加快验证回路 |
| 风险判断 | 靠经验踩坑 | 靠数据说话,能列出影响范围再动手 |
| 团队要求 | 需要核心架构师深度参与 | 普通开发在明确指令下也能推进 |
这个表不是想说明 AI 比人强,而是说明它能把“读代码、找影响面、做重复修改”这些重体力活消化掉,让人把精力放在真正需要判断力的地方。重构这件事最大的瓶颈从来不是写代码,而是对系统现状的把握和对改动范围的信心,这两点恰恰是 AI 最能补位的。
2. 重构前的代码库体检:到底能不能动,先让 AI 给你一份体检报告
2.1 先让 Claude Code 把代码库整体“读”一遍
任何重构工程的第一步都不是写代码,而是先搞清楚现状。这一步我通常叫它“代码库体检”。用 Claude Code 做体检,不是简单地让它生成一个 README 或者目录树,而是要通过几轮有目的的对话,把它变成你的“第二双眼睛”。
第一次对话,我会先给它一个宏观任务,类似这样:
请扫描当前仓库(重点是 src/ 和 services/ 目录),输出: 1. 项目整体的模块划分和依赖关系; 2. 哪些目录/文件看起来是核心业务逻辑,哪些是胶水代码; 3. 是否存在循环依赖或者特别臃肿的类(超过 1000 行的重点标出); 4. 项目里有没有明显废弃的入口、注释掉的代码、不再使用的配置项。这种开放式的提问会得到一份挺有参考价值的“地图”。在此基础上,我会让它针对每一个重点模块展开第二轮追问:“支付模块的入口是哪几个类?”“订单状态字段在哪些地方被读写?”“数据库表的变更历史能不能从代码里推断出来?”你会发现,它给出的答案里会带着具体的文件路径和行号,这时候你就能拿这些问题去抽查代码,验证它的理解是否准确。
一个很关键的技巧是:不要一次性让它把整个仓库都读完。遗留系统往往体积庞大,一次塞太多内容,处理效果会明显下降。我的做法是先列目录结构,再让它按业务模块一个个深入。比如先了解账户体系,再了解订单体系,最后再看二者如何交互。分模块推进,效率和准确率都高很多。
还有什么值得做?让它生成一张“技术债清单”。你可以直接问:
基于现有代码结构,列出你认为最需要重构的 10 个位置,并按业务风险从高到低排序。 给出每个位置的问题描述、涉及文件、以及如果引发故障可能影响的业务功能。这个输出很有价值,因为 Claude Code 不但能看代码,还能结合文件名、注释、调用关系做关联推理,给出的排序通常比人工凭印象列出来的更贴近实际。我拿它和团队内部评估做过对比,它的清单里确实覆盖到了几个平时不太关注的角落,比如某个只有定时任务在调用的老工具类、某个异常处理逻辑被多层透传的 service。
2.2 量化指标:用数据判断“病”到了什么程度
光靠 AI 的定性描述还不够,重构决策需要量化依据。我在体检阶段会拉一组指标出来,它们不需要特别精确,够用来横向对比模块之间的健康度就行。
| 指标 | 具体含义 | 采集方式 |
|---|---|---|
| 圈复杂度 | 单个方法的逻辑分支数量,越高越难维护 | 用 lint 工具或让 Claude 读取代码估算 |
| 代码重复率 | 相似代码块在各模块间的重复程度 | 用静态分析工具扫描 |
| 文件行数分布 | 体量臃肿的类集中在哪些业务域 | 脚本统计 + AI 标注 |
| 依赖深度 | 模块之间的调用层级和耦合程度 | 通过 import 关系可视化 |
| 测试覆盖率 | 核心模块的自动化测试覆盖情况 | 跑一次覆盖工具 |
| 改动频率 | 哪些文件在最近一年内被频繁修改 | 翻 git log |
拿到这组数据后,我会让 Claude Code 帮忙做交叉分析,比如:
结合上面的复杂度数据和 git 提交历史,找出“最近一年改动最多且圈复杂度最高”的 5 个模块。 同时分析这些模块各自依赖了哪些底层组件,如果一个模块被改动,影响范围大概有多大。这样筛出来的模块,才是真正值得优先重构的对象。这里有一个反常识的经验:改动最频繁的模块,不一定是最该重构的模块。有些模块虽然改得多,但每次都是小打小闹,风险可控;真正危险的是那些“长期没人动、但所有新功能都绕不开”的死结模块,因为意味着任何一次改动都可能引爆积累多年的隐患。
2.3 用可行性矩阵决定“重构 / 替换 / 不动”
体检完成后,面对几十个模块,不能一股脑全上。我的做法是拿“业务价值”和“技术风险”两个维度做一个可行性矩阵,把模块分成三类。
第一类是“值得重构”的模块。特征是业务价值高、代码质量差、被大量周边系统依赖。这类模块重构后收益最明显,但也是风险最大的,需要放慢节奏、加强验证。第二类是“直接替换”的模块。特征是和核心业务集成度低,比如内部报表工具、废弃的管理后台,或者功能可以买到现成方案的部分,直接换新实现,旧代码逐步退役。第三类是“暂时不动”的模块。特征是没有明确迭代诉求、没有新需求驱动、改动风险又高。这时候重构纯粹是给自己找活干,不如把它先列入观察清单。
这个矩阵可以让 Claude Code 做初筛,但最终拍板得人来。AI 适合做的事是给出“技术风险”的数据依据,而“业务价值”只有熟悉业务的人才知道。我会在会议上把 AI 生成的分析发出来,结合团队对业务的理解来确认优先级。实践证明,这种方式比纯粹靠架构师个人经验靠谱得多,因为每个人的盲区不同,而 AI 至少能保证信息维度是完整的。
做完这三步,你对系统“哪些地方能动、哪些地方碰不得”已经有了清晰的底图。接下来才是设计动作方案。
3. 分阶段落地:从评估到切换的实操流程
3.1 为什么采用“绞杀者模式”而不是一步到位
很多团队一提重构,第一反应是“开个新分支,把代码全部重写一遍,最后整体切换”。这个思路对遗留系统来说几乎必死。原因不复杂:老系统里藏着大量“没有文档但线上跑得好好的”隐性逻辑,你重写时根本没意识到它们的存在,直到上线后用户告诉你“某个字段以前是这么算的”。
我在实践中比较推崇的,是所谓的“绞杀者模式”。名字听着吓人,实际思路很朴素:不推倒老系统,而是在老系统旁边慢慢建一个新系统,新功能直接在新系统里实现,老功能逐渐迁移,每完成一部分,就把老系统的路由切到新系统上,一次切一小块。就像老城改造:不在同一条街上同时拆掉所有房子,而是先在新城盖楼,盖好一栋搬一栋,最后才拆老城区。
这个模式对 Claude Code 这类工具特别友好,因为它的核心工作方式是“小步重构、快速验证”,正好避开 AI 处理超大型任务时容易出错的短板。一次只让 AI 处理一个模块、一条链路、一个业务场景,既能保证输出质量,又能把风险控制在单个功能范围内。再加上可以随时让 AI 生成回归测试来兜底,整体推进速度比传统方式快很多。
3.2 搭建过渡期双轨运行的关键动作
用绞杀者模式的第一步,是让新老系统能够同时运行。注意不是简单地把新代码写在另一个目录里,而是要解决三个实际问题。
第一个问题是路由。同一个外网入口,要能根据参数或者用户维度,把一部分请求导向老系统,另一部分导向新系统。我当时用一个轻量级的网关层做这件事,核心逻辑只有几十行:读一个开关配置,判断当前用户是否属于灰度名单,然后转发到对应系统。这个阶段不用做得太复杂,能支持“按用户”、“按百分比”两种切流方式基本就够了。
第二个问题是数据。老系统和新系统如果用同一个数据库,那交互逻辑会比较简单,风险是两边代码同时改数据可能踩锁;如果用了不同的存储,就要解决数据同步和双写问题。我的建议是,初始阶段尽量沿用同一个数据库实例,等新系统稳定了再考虑数据迁移。一次只引入一个变量,这是系统改造的铁律。
第三个问题是契约。新老系统之间可能需要互相调用对方的接口,比如订单模块已经迁到新系统了,但库存模块还在老系统,订单流程里要查库存时,新系统得去调老系统的接口。这时候我会让 Claude Code 先把老系统的接口契约整理成一份清单,包括入参、出参、错误码和超时时间,然后根据这份契约生成新的客户端代码。有了契约,两边团队并行开发时就不会“各说各话”。
这个阶段我会花不少时间让 Claude Code 打磨细节。比如让它检查老系统接口是否有隐式依赖——有些接口虽然参数里没有 userId,但会从 session 里取当前登录人,这套逻辑在双轨运行时会变成大坑。Claude 能从代码里识别出这类“隐藏参数”,提前暴露问题,比上线后才被发现好得多。
3.3 让 Claude Code 完成模块翻译的实操流程
当基础设施就绪后,就进入最核心的阶段:让 Claude Code 把老模块逐个翻译成新架构。很多人的第一反应是直接给它一个指令:“把 OrderService 重写一遍。”这样做的效果通常不咋地,因为它缺少足够的上下文。我的操作流程基本固定为四步,每步都验证通过后再进入下一步。
第一步,先让 Claude Code 把老模块的完整逻辑“复述”一遍。指令大概是这样的:
请阅读 services/order/ 下的所有文件,用中文详细说明当前订单模块的核心流程: 1. 从创建订单到支付成功再到退款,每一步经历了哪些类和状态; 2. 每个状态字段的取值、流转条件、以及写库的位置; 3. 异常分支和边界情况(库存不足、重复支付、超时取消等)是怎么处理的; 4. 与其他模块(用户、支付、物流)的交互点在哪里。这一步的输出就是下一轮对话的上下文基础。它相当于让 AI 先“读懂”旧代码,我们拿到它的理解后先做 review,确认没有遗漏后再动工。如果这一步发现 AI 的理解和实际代码有出入,宁可停下来修正,也不要直接去改代码。
第二步,让 Claude Code 在新项目的结构下生成等价实现。注意措辞是“等价实现”,不是“凭空重写”:
基于上面梳理的流程,在 modules/order/ 下按照新项目的分层规范,重新实现订单模块。 约束条件: - 保持核心业务流程和原系统完全一致; - 消除原有代码中的全局状态和静态方法; - 把外部依赖抽象为接口,便于后续 mock; - 保持与当前数据库结构兼容,不改表结构。这个阶段最重要的是给足约束。AI 模型有很强的“自我发挥”倾向,不给约束它就敢顺手给你“优化”掉一个边界判断。我踩过最深的坑,就是它自作主张把原来一个状态机的状态名合并了,看起来简洁了,但老系统里其他地方还在往这个字段写旧值,结果上线后出现数据错乱。所以指令里一定要强调“保持兼容、保持语义不变”。
第三步是让 AI 生成配套测试。不是简单让它“为函数写单测”,而是给它具体场景:
为订单模块的 create/refund/cancel 三条链路分别编写单元测试。 测试用例覆盖: - 正常流程; - 库存不足、非法状态、重复操作等异常分支; - 老代码中发现过的历史 bug 场景(见上面提到的问题列表)。如果项目里没有现成测试框架,也可以趁机让 Claude Code 帮忙引入一套轻量的测试体系。老模块没测试不是借口,AI 生成的测试哪怕覆盖率初始不高,也能成为后续改动的安全网。
第四步,让 Claude Code 跑老模块和新模块的“行为对比”。这一部很巧妙:直接生成一段对比脚本,用同一组测试数据分别调用老系统和新的模块实现,比对返回结果和数据库写入情况是否一致。虽然做不到业务全覆盖,但核心场景的一致性验证能给你很强的信心。
整个流程走下来,一个中等复杂度的业务模块大概一周内就能完成迁移,而传统方式往往需要一个月起步。差距主要不在“写代码”的速度,而在“理解代码”的速度——AI 读代码的能力远比人快,而人能做的判断和约束,恰恰是质量保障的关键。
3.4 每个阶段怎么验收,才不会被“看起来能用”骗了
分阶段迁移有一个隐性问题:什么时候算“这个模块迁移完成”?如果标准只是“功能评审通过了”,往往过不了几天就会被线上问题打脸。我自己总结了一套验收标准,每个模块转移到新系统时都按这个清单过一遍。
业务功能验收是最基本的,核心流程和边界异常都要人工走一遍,数据和老系统对拍一致。代码质量验收则看新模块的圈复杂度是否明显低于老模块、有没有引入新的坏味道、依赖关系是否清晰。而这其中最容易造假也最容易被忽略的,是回归验证:把老系统过去半年内处理过的真实线上请求,抽出一批在新系统上重新跑一遍,比较最终写入的数据和返回结果。这个做法比任何手工测试都管用,因为线上流量里包含的边界条件是你写测试时根本想不到的。
另外,灰度切换本身就是一次验证。不要把老系统立刻下线,先让 5% 的流量走新系统,观察几天;没问题再逐步放大比例,最后到 100%。任何一步发现问题,都能立刻回滚路由,让流量回到老系统。这种安全感,是全量切换给不了的。
4. 常见问题与排查技巧实录:那些文档里不会写的坑
4.1 上下文窗口不够用,AI 理解到后面忘前面
使用 Claude Code 处理大型模块时,最常遇到的就是“它看到后面忘了前面”。一开始我以为是随机问题,后来发现规律:模块文件一旦超过某个量级,它就很容易在前一步结论的基础上做出错误推理。典型场景是:你让它梳理“用户注册到首次下单”的完整流程,它讲到一半突然把注册的状态和下单的状态搞混了。
解决办法有两个。第一个是拆得更细。别让它一口气分析整个链路,而是先梳理“注册”,确认无误后再梳理“下单”,最后让它基于前两轮总结出来的中间结果做串联。第二个是让它“写总结”而不是“记原文”。每完成一个子任务,就让它把关键结论写成一个 markdown 文件存到项目里,下一轮对话里直接引用这个文件,而不是依赖上下文记忆。
我现在的习惯是:每个模块建一个 docs/refactor/ 目录,Claude Code 每产出一份分析,就让它同时写入这个目录。这样既方便人工 review,也能避免对话上下文丢失导致的“失忆”。这个习惯帮我节省了大量重复沟通的时间。
4.2 老代码里的“坏味道”让 AI 无从下手
遇到乱到一定程度的老系统,Claude Code 也会“卡壳”。我记得有个模块里全是 2000 行的类、无穷无尽的 if-else、还有从不同层透传的 null,让它分析这段代码的逻辑,它给出来的答案含含糊糊,语句里全是“可能”“应该”。
对付这种代码,一个比较有效的办法是“先重构到能理解,再理解后重构”。让 Claude Code 先做机械性的重构:把超大方法拆成小方法、把魔法数字提取成常量、把重复代码抽取成函数。这些改动不改变逻辑,但能让结构清晰很多。等代码可读性提升后,再让它做业务流程梳理,准确率会明显改善。
这里要注意一个操作边界:机械重构虽然不改变逻辑,但依然属于“改动代码”。每一步都要让 Claude Code 生成对应的差异说明,人工 review 后再提交。不要让它一口气“优化”完一整个大文件,一次改动尽量控制在几十行以内,出错了好排查。
4.3 没有测试的老模块,怎么判断“改对了”
前面提过,老项目最大的历史遗留问题就是没有测试。没有测试的情况下,任何一次“重构完成”都可能是定时炸弹。我的兜底方案是三板斧。
第一板斧是特性测试。让 Claude Code 基于代码现状,把可观测到的输入输出组合整理成数据驱动测试。比如一个计算订单金额的方法,把所有折扣组合、边界金额、异常输入枚举出来,做成测试用例。这些用例不来自需求文档,而是来自代码本身,目的是把“当前行为”固化下来。
第二板斧是差异对比。在双轨运行期间,新老系统跑同一批请求,自动比对结果。我当时会让 Claude Code 写一个小工具,读取线上日志中的请求参数,重新分别发给老接口和新接口,再对响应做规范化比对。差异为 0,就是最好的回归测试。
第三板斧是快照测试。把老系统对固定一批数据的处理结果保存为快照文件,新系统把同一批数据跑一遍,逐字段对比快照。这个方法对“重构后不许动”的场景特别有效,虽然它的维护成本会高一些,但作为切换前的最后防线,值。
4.4 权限边界:别让 AI 在线上环境“自由发挥”
Claude Code 这类工具具备执行命令、修改文件的能力,这既是效率来源,也是风险来源。尤其当你连接的是包含生产环境配置的仓库时,一定要谨慎设置权限。
我的建议是三条硬性规则。第一,在项目配置里明确限制它只能操作指定目录和指定命令白名单,禁止对生产环境的直接操作。第二,代码变更一律通过本地分支完成,AI 的改动全都要走 pull request 流程,由真人 review 后再合入。第三,不要给它“你全权处理”这种模糊指令,每一步的关键动作都要限定范围。
实践中我发现一个很实用的配置:让 Claude Code 在改代码之前先输出一份“我计划修改这些文件,原因如下”的说明。等确认后,再让它实际动手。多这一道交互,成本不高,但能避免大部分“AI 自作主张改了不该改的地方”的情况。
4.5 问题速查表
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| AI 分析结果前后矛盾 | 上下文过长或模块过于复杂 | 拆分成更小的分析单元,输出中间结论到文件 |
| 改动代码后老逻辑丢失 | 约束条件不足,AI 默认“优化”了它认为不合理的地方 | 在提示词里明确“保持语义不变”并逐条 review diff |
| 对老接口理解偏差 | 调用链太长,AI 没读全上下游 | 让它先画出调用关系图,人工确认后再翻译 |
| 生成的测试覆盖率极低 | 对业务的不确定性导致测试偏向“快乐路径” | 提供历史故障场景、边界参数,直接驱动生成异常用例 |
| 双轨运行时数据不一致 | 新老代码对同一事件的处理顺序或原子性不同 | 先看事务边界,再对拍接口数据,逐步缩小差异范围 |
| AI 回复越来越慢且开始“敷衍” | 对话上下文过载,模型生成质量下降 | 新开对话,把关键结论整理成文档后重新引导 |
把这几个问题提前讲给团队听,能让整个重构过程少走很多弯路。我在第一个项目里几乎把这些坑全踩了一遍,等到第二个项目,流程顺了,大家的心态也稳了很多。
最后再说一点个人体会。重构遗留系统这件事,最大的敌人其实不是技术复杂度,而是团队心理障碍——觉得代码太乱、范围太大、风险太高,干脆拖着不动。Claude Code 这类工具并没有把重构变成一件“毫无风险”的事,但它确实把“理解现状”的成本降了一个数量级,让人更容易迈出第一步。我的建议是不要想着一次搞定所有模块,挑一个中等复杂度、业务影响可控的模块,用我上面说的这套流程完整跑一遍。等你验证过这个模式有效,再逐步推广到核心模块,整个工程的节奏就掌握在自己手里了。