修流程,不修代码:Anthropic 如何用 Claude Code 跑大规模代码迁移

从 Bun 的 Zig→Rust 到 Python→TypeScript:一套把'迁移'从数年豪赌变成一周试验的方法论

Posted by iceyao on Saturday, July 18, 2026

引言:你修的不是代码,而是产出代码的循环

原文链接:How Anthropic runs large-scale code migrations with Claude Code

来源:Claude Blog / Anthropic

发布时间:2026 年 7 月 16 日

代码迁移——把一个生产级代码库整体移植到另一门语言——过去是所有工程师都知道该做、但没人敢碰的那类事。它太贵、太慢、太容易半途而废。可就在过去一个月里,Anthropic 的开发者用 Claude 迁移了 10 个代码包,规模从数万行到数十万行不等,其中一个甚至在两周内产出了超过 100 万行代码。

这篇官方文章最值得记住的,不是这些数字,而是它反复强调的一句心智转变:

你不修代码,你修的是产出代码的那个流程(循环)。

修流程,不修代码

这句话是理解全文的钥匙。传统迁移里,人是"翻译者"——一行行把旧语言改写成新语言,遇到 bug 一个个手动修。而在这套方法论里,人退到了"流程设计者"的位置:你搭一条"实现 → 审查 → 修复"的流水线,让上百个 agent 在里面跑;你盯的不是某个具体文件对不对,而是这条流水线产出的东西是否系统性地正确。一旦发现某类错误反复出现,你不去逐个改文件,而是回去改产生它的那条规则。

下面按四条线索拆解:迁移的经济账为什么变了、为什么这件事恰好适合 AI、六步方法论到底怎么走,以及两个真实案例给出的不同答案。


一、迁移的"经济账"被彻底改写

要不要迁移语言,从来不是一个技术问题,而是一道成本题。决定这道题的两个变量是:总成本,和失败时的最坏结果。AI 把这两个变量都改写了。

迁移的经济账变了

过去一次严肃的语言迁移,动辄横跨四年、烧掉 300 到 400 万美元。更劝退的不是钱,而是过程中的姿势:你必须同时维护新旧两套代码库长达数个季度乃至数年,而它们最终往往只能做到约 90% 一致。这意味着任何一次迁移都是一场职业级的豪赌——要么成功,要么背锅。所以传统上,只有当旧技术栈"再不换就活不下去"时,团队才敢立项。

AI 把这三件事全改了:

  • 周期从数年压缩到一到两周;
  • 成本降到数万到数十万美元的量级——仍然需要一个正当的商业理由,但门槛低了两个数量级;
  • 最关键的是最坏结果:不再是"背着两套代码库拖垮团队",而是——删掉分支,重来一遍

于是原文给出了那句我认为最重要的判断:

迁移的理由,不再需要是生死存亡级别的。

变更日志里那个躺了一年的内存 bug、那个你忍了很久的编译瓶颈,现在都足以成为立项的理由。迁移从"背水一战"变成了"值得一试"。


二、为什么大规模迁移,天生适合 AI

不是所有任务都适合甩给一群 agent 去跑。大规模迁移之所以成为 Claude 的"主场",是因为它同时具备了五个特性——而这五个特性恰好长成了 agent 最擅长的形状。

大规模迁移为何天生适合 AI

  1. 工作可并行:迁移天然可以拆成数千个独立单元(文件、crate),彼此之间没有强耦合,可以同时扇出给成百个子 agent。
  2. 旧代码即规范:这是迁移区别于"从零开发"的核心优势——你不需要凭空写 spec,已经存在的旧实现本身就是最完整、最无歧义的规范,也是翻译 agent 的核心参考。
  3. 内置裁判:很多成熟代码库自带测试套件,agent 可以据此客观地验证对错。原文一句话点出了关键:模型在"验证客观"时表现最好,可以对着基准真相磨上好几天,而无需人来仲裁质量
  4. 队列自生成:编译失败、测试失败,本身就是下一个待办项。工作队列不需要人来排——它是被错误信息喂出来的。
  5. 一致性无处藏身:流程设计让"漂移"(drift)暴露无遗。审查者为每一个发现都引用一条规则,违规就变成队列项而非悄悄分歧;某个 agent 处理了一个边界情况,它的修复会成为后续所有 agent 遵循的规则。

把这五点串起来的,是同一个前提:验证必须客观。让编译器、diff、测试套件来当裁判,而不是让人去主观打分——只有当"对与错"可以被机械判定时,模型才能在无人值守的情况下连续自我纠错。这也是把人从"逐个评审几十万行代码"里解放出来的根本原因。


三、六步流程:先建裁判,再让队列自己燃烧

原文把实践泛化成了一套六步流程。但在第一步之前,有一个前置条件比任何一步都重要。

六步流程

前置条件:先造一个抓得住破坏的裁判

没有裁判,就没有退出条件,也没有成功的度量。裁判必须能在同等基础上评估旧代码和新代码。构建方法分三步:

  1. 归类现有测试:用 Claude 识别哪些测试可以表达为外部调用,哪些依赖了不会被移植的内部实现;
  2. 改写以增强可移植性:把面向外部的测试改写成"能同时跑在新旧两套代码上"的断言,并用一个对抗式 agent 验证改写后没有偷偷弱化断言
  3. 验证裁判本身:先对原代码跑,确认全部通过;再对一份"故意改坏的代码"跑,确认它会失败

这一步有一句话值得贴在墙上:

抓不到破坏的裁判,不是裁判。

六步逐一拆解

  • 步骤 1 · 立规则:产出三样东西——规则手册、依赖图、缺口清单。顺序很重要,规则手册必须在缺口清单之前,因为缺口清单本就是"规则手册默认覆盖不了的部分"。规则手册的形态取决于架构决策:如果新旧同构,它基本是一张"语言间类型/惯用法查找表";如果要重设计,它就是一份设计文档。依赖图用来切分并行工作流;缺口清单则记录新旧语言的根本差异——比如 Zig→Rust 的差异是手动内存管理(Zig 忘记 free 也能编译,泄漏到运行时才暴露;Rust 的所有权模型让用错直接编译不过),Python→TypeScript 的差异是接口与契约(Python 不必声明对象形状,TS 必须先把契约写下来才能编译)。
  • 步骤 2 · 压力测试规则:一次小规模的"试航"。Jarred 的做法很聪明:让一个 agent 严格照规则手册翻译 3 个文件,另一个 agent “像资深 Rust 工程师那样"自由翻译同样 3 个文件,第三个 agent 拿两份译文做 diff、从差异里提炼出新规则。这一步他抓到了 2 个关键问题——如果这两个问题被扇出到全部 1448 个文件,后果不堪设想。注意:这一步产出的译文必须全部丢弃,因为目标是打磨规则,而不是攒进度。
  • 步骤 3 · 翻译一切:跑起同一套多 agent 循环(下一节详述)。这里工作队列要机械化且可恢复——批处理脚本靠"目标文件是否已存在于磁盘"来判断完成,队列每次从磁盘重建,所以迁移天生可以断点续跑。翻译器搞不定的地方,用 // TODO(port): <原因> 标出来,留给后面的步骤。
  • 步骤 4 · 编译:常常和步骤 3 合并。编排脚本对整个工作区一次性调用编译器,修复 agent 并行处理错误列表。审查错误列表能揪出系统性问题——比如 Jarred 修掉一处"Zig 惰性编译容忍、Rust 不容忍"的循环导入后,一下子浮现了数千个 Rust 模块错误,他通过编码逻辑分类"删除 / 移动 / 重构边界"来批量解决。
  • 步骤 5 · 运行:机械真相的来源变成了冒烟测试里的崩溃。修复循环按根本原因分组,再交给对抗式子 agent 审查。
  • 步骤 6 · 匹配行为:分片跑测试套件,逐命令比对新旧两套代码的输出。这一步引入了一个关键角色——build daemon(构建守护进程):它是唯一被允许重建二进制的进程,修复者只管写补丁,守护进程批量重建一次、重跑受影响的测试、反馈结果,把最昂贵的操作串行化

一条贯穿三到六步的铁律是:修复永远上移,代码永不手工打补丁。当审查者反复抓到同一类错误,正确的动作不是逐个文件去改,而是回到规则手册里加一句话,然后只重新生成受这条规则影响的那批文件。规则手册在整个过程中持续生长。


四、循环的引擎:实现 → 审查 → 修复

上面六步里,步骤 3 到 6 共享的是同一套循环架构。理解了这个循环,就理解了整套方法论的发动机。

实现-审查-修复循环

循环有三个环节,但真正的设计巧思在于给不同环节配不同大小的模型——因为 token 几乎全烧在这个循环里,模型分配必须刻意为之:

  • 实现较小模型做高量扇出。Mike 在主迁移时用 Sonnet 扇出十几个子 agent 并行翻译。agent 有时会过于保守,这时用"直白强硬的提示 + 反正编译器下一步会抓错"的上下文把它推一把。
  • 审查较大模型,而且是两个对抗式审查者、各自独立上下文互相挑刺,意见分歧时交给第三个 agent 仲裁。审查者被要求为每一个发现都引用一条具体规则。
  • 修复根本原因分组,修复 agent 处理错误列表,再由对抗审查者复核补丁。

而"修复上移"的回环是这套循环最反直觉、也最高效的部分:同一类错误重复出现时,修的不是文件,是规则——在规则手册里加一句话,然后重新生成受影响的批次。这让修复的边际成本随规模下降,而不是上升。

还有一个容易被忽略但很实际的设计决策:编译器摆在哪里。Mike 把 TypeScript 编译器放进每个循环内部(tsc 秒级,即时反馈即时纠偏);Jarred 则完全禁止 cargo 进入循环、推迟到独立的编译步骤(因为它要跑好几分钟,进循环会拖垮吞吐)。同一个问题,两种相反的答案——取决于你的编译器有多快。


五、两个案例的分野:保结构 vs 重设计

原文最有价值的地方,是它给了两个走向截然不同的真实案例。它们用的是同一套方法论,却在几乎每个关键决策上选择了相反的方向。

两个真实案例的分野

案例 A:Bun 的 Zig → Rust(Jarred Sumner) 走的是保结构翻译——新旧代码大体同构,规则手册基本是一张类型与惯用法的查找表。因为结构对齐,他能用"两份译文 diff"的方式压测规则。他有一套现成的、甚至用第三门语言 TypeScript 写的大型测试套件当裁判,于是采取"一次跑通"策略:合并前 Bun 测试 100% 通过 CI,合并后浮现 19 个回归、随后全部修复。代价是约 59 亿输入 token + 6.9 亿输出 token,按 API 定价约 16.5 万美元

案例 B:内部服务 Python → TypeScript(Mike Krieger) 走的是完全重设计——规则手册是一份设计文档而非查找表。因为结构不再对齐,“两份译文 diff"失效了,他改用对抗式审查者直接攻击设计文档。他手里没有现成测试套件,就让 Claude 造了一个:含 7 个真实场景的一致性测试装置,任何行为差异都算待修 bug。他的策略是"端到端跑通、根据结果修订规则、整体重跑”,每次丢弃产出,直到第三次运行才成功。主体移植约 2700 万 token

他做迁移的动因也很典型——编译速度:Python 工具链为每个平台产出单一二进制约需 8 分钟,整个构建矩阵每次发布要等 30 分钟;移植后编译只需约 2 秒,二进制启动快 6 倍,还顺手退役了一整条部署流水线。

而对"没有现成测试套件"这个绝大多数团队都会遇到的现实,原文的态度很干脆:

缺失测试套件不会阻塞这一步。如果继承不到裁判,就让 Claude 造一个。无论如何,你的原始代码库就是基准真相。


六、结果与最佳实践

Jarred 的 Bun 迁移已经投入生产。结果值得摆出来看——因为这套方法论最终要靠可度量的产出来证明自己。

Bun 迁移的可度量结果

  • 内存:一项 2000 次重复构建的基准,内存占用从 6745 MB 降到 609 MB;团队工具能检测到的每一个内存泄漏都被修复了;
  • 体积:二进制在 Linux 和 Windows 上都小了 19%
  • 性能:跨语言优化让 HTTP 服务以及 next buildtsc 等真实负载快了 2–5%

当然也有真实的权衡:约 4% 的 Rust 代码位于 unsafe内,多为 C/C++ 边界上的单行指针操作。没有零成本的迁移,只有账算得过来的迁移。

原文最后沉淀的几条最佳实践,几乎每一条都在重复"修流程不修代码"这个主旨:

  • 别盲目照搬指南:每次迁移都不一样,把它当起点,动手前先和 Claude 一起规划;
  • 别盯着单个失败:单点失败是循环的活,交给修复 agent;你的注意力应该放在模式上;
  • 审查要对抗,验证要机械:让编译器、diff、测试套件当裁判;
  • 别对所有事都用最大模型:小模型擅长高量实现扇出,把最大模型留给审查者和"写规则给别的 agent 遵循"这类任务;
  • 把人力前置:规则手册和压力测试最耗时,之后大多是队列在自己燃烧;
  • 让工作队列机械化且可恢复:“完成"应当意味着"输出文件已经躺在磁盘上”。

结语:重新算一算那些被搁置的迁移

这篇文章真正想传递的,不是"AI 能写很多代码”,而是代码迁移的决策模型已经变了。当失败的代价从"数年 + 职业风险"降到"删个分支重来",那些你一直忍着、一直没排上优先级的技术债,突然都变得"值得一试"。

原文结尾的呼吁很朴素,也很有说服力:

挑一个你一直忍受着的代码库,问问 Claude 它的迁移流程会长什么样。

值得补充的是,Anthropic 开源了一个迁移入门套件作为这套流程的泛化模板——但要注意,它是模板,并非上述那些具体移植实际运行时所用的东西。真正的方法论不在套件里,而在那句话里:你修的不是代码,是产出代码的循环。

「真诚赞赏,手留余香」

爱折腾的工程师

真诚赞赏,手留余香

使用微信扫描二维码完成支付