跳到正文
北京时间
原文
Claude:Blog(网页)·· 2026-07-16精选AI 评分65

Anthropic 用 Claude Code 大规模迁移代码:Bun 百万行 Zig 转 Rust,两周完成

How Anthropic runs large-scale code migrations with Claude Code

AI 导读

Anthropic 工程师用 Claude Code 在两周内将 Bun 的百万行 Zig 代码迁移至 Rust,100% 现有测试通过,合并后出现 19 个回归问题已全部修复。另一工程师用周末将 Python 代码库迁移至 16.5 万行 TypeScript。迁移消耗约 16.5 万美元 API 成本,但编译时间从八分钟降至两秒,二进制启动快 6 倍。

推荐理由

Anthropic 用自家 Claude Code 把百万行 Zig 代码迁到 Rust,两周搞定,这不仅是案例展示,更是开发者如何用 AI 重构代码库的实操指南,尤其适合那些被遗留代码拖累的团队。

正文 · AI 翻译

代码迁移,即把生产代码库移植到新语言的项目,直到最近都还是需要耗费数年的工程。

在过去一个月里,Anthropic 的独立开发者们使用 Claude Fable 5、Claude Opus 4.8 以及动态工作流,迁移了 10 个代码包,包含从数万到数十万行的代码。在本文中,我们将介绍两个示例,以及从这些项目中总结出的最佳实践。

Bun 联合创始人、Anthropic 技术团队成员 Jarred Sumner 使用 Claude Code 将 Bun 从 Zig 迁移到 Rust。不到两周就产出了上百万行代码,且在合并前,Bun 现有的全部测试套件在 CI 中 100% 通过。合并后出现了 19 处回归,现已全部修复。Rust 移植版已于 6 月在 Claude Code 内发布。

Anthropic Labs 联合负责人 Mike Krieger 在一个周末内将一个 Python 代码库迁移为 165,000 行 TypeScript。这包括数百个智能体、八道阶段关卡、三轮对抗性审查,以及最后一项一致性检查——将每条命令的输出与 Python 原版逐一比对。

Claude Code 的新能力改变了这些长期被搁置的项目的可行性。以下是我们现在采用的六步流程,源自这些迁移项目带给我们的经验。

核心洞见在于,你要修的不是代码。你要修的是产出代码的那个流程(循环)。

为何以及何时迁移语言

在直接进入怎么做之前,值得先讨论什么时候做以及为什么做,因为围绕这些项目的假设已经发生了变化。

团队之所以启动迁移,是因为从最初构建到当前项目之间,技术格局发生了变化。要么是某个已知的权衡已经变成了瓶颈,要么是出现了更好的方案,要么是原有的生态系统正在萎缩。

例如,Jarred 最初选择 Zig,是因为它提供了 C 级别的性能,同时极其简洁,非常适合一位独立创始人“在 LLM 出现之前,在奥克兰一间狭小的公寓里用 1 年时间写出 Bun”。这种简洁性伴随着已知的权衡,他在此文中有所论述。

快进到 2026 年。Bun 的 CLI 月下载量已超过 1000 万次,并在 Claude Code 中被广泛使用。

就在上个季度,这些权衡还不足以成为冻结路线图并将资源投入一个跨季度项目的理由。迁移语言可以带来更小、更快、更安全的系统,但没有人愿意为此付出代价。

软件工程师还不得不面对这些曾经的巨型项目所固有的职业风险。你可能需要维护两套并行的代码库长达数个季度甚至数年,而如果最终结果是 90% 的功能对齐,你面临的麻烦比开始时还要大。

而现在,最坏的情况无非是删掉分支重新来过。

仍然需要有一个站得住脚的商业理由。虽然百万行级别的迁移不再需要在一个为期四年的项目中花费 300 万到 400 万美元的工程资源,但执行起来仍然要花费数万到数十万美元甚至更多。例如,Bun 迁移消耗了 59 亿未缓存的输入 token 和 6.9 亿输出 token——按 API 定价约为 16.5 万美元。Mike 移植工作的主要部分用了 2700 万 token。

Jarred 的百万行 PR。

然而,迁移的理由不再需要是生死攸关的。变更日志中一年的内存 bug 补丁,或者一个长期存在的瓶颈,如今就足以成为理由。

编译步骤是 Mike 项目的推动因素。他团队所开发的内部工具以单一二进制文件的形式交付给用户。用 Python 工具链生成该二进制文件在每个平台上大约需要八分钟,每次发布在整个构建矩阵上总共要等待 30 分钟。移植之后,同样的编译现在只需约两秒,二进制文件启动速度快了 6 倍,团队还得以退役一条单独的部署流水线。

为什么 AI 改变了代码迁移的账本

Claude Fable 5 是我们能力最强、已普遍可用的模型。Fable 和 Opus 4.8 尤其擅长通过子智能体来委派、指挥和验证并行工作流,同时为既定目标寻找多条路径。

大型代码迁移是这些先进模型尤为高效的一类用例,原因在于:

  • 工作是并行的。工作可以拆分成数千个独立单元(例如文件和 crate)来执行,因此各个智能体可以同时工作,而不必一个等待另一个。
  • 上下文清晰且全面。旧代码为模型提供了极好的规格说明。它同时也是一份核心参考,帮助构建供翻译智能体遵循的指南。
  • 存在内置的裁判。许多大型代码库都会包含一套测试套件,智能体可以用它来验证自己的工作。当验证是客观的时,智能体表现最佳,因为模型可以连续数天针对一个基准真值反复打磨,而无需人类来仲裁质量。
  • 队列会自行生成。当编译器或测试运行失败时,这就会成为智能体下一个要修复的条目。
  • 它们要求一致性和边界情况处理:整个流程的设计让偏差无处藏身:审查者会为每一项发现引用其背后的规则,因此一次违规会变成一个队列条目,而不是一次悄无声息的偏离。而当智能体确实遇到边界情况时,修复方案会成为后续每个智能体都遵循的规则。

正如我们将在下文看到的,Mike 和 Jarred 都在迁移过程的关键步骤中使用了 Fable,尤其是在一种顾问模式中,该模式使用多个模型类别来优化 token 消耗。

视频 · 前往原文观看

大型代码迁移的六个步骤

以下流程已被泛化,以适用于多种语言和场景。如需更多细节,你可以阅读Jarred 的博客。

前置条件

在开始迁移项目之前,一个前置条件是必须有一个强大的评判器,否则你就没有退出条件或成功衡量标准。

评判器必须能够以同等标准评估原始代码和目标代码。用原始语言编写的测试套件通常依赖于目标代码中不存在的内部函数。

要构建这个评判器:

  • 对现有测试进行分类。使用 Claude 识别哪些测试可以表达为外部调用,哪些依赖于无法移植的内部实现。
  • 为可移植性而重写。 将面向外部的测试转换为可同时针对原始版本和移植版本运行的断言。使用对抗性智能体来验证重写后的测试没有削弱断言。
  • 验证评判器。针对原始代码运行它以确认其通过。然后针对故意破坏的代码运行它以确认其失败——一个无法捕获破坏的评判器算不上评判器。

Jarred 有一个用第三种语言(TypeScript)编写的大型测试套件,但大多数项目不会有这种情况。对于他的 Python 到 TypeScript 移植,Mike 创建了一个包含七个真实场景的对等性测试框架,并将任何行为变化都视为需要修复的 bug。

在我们进入每个阶段之前,这张图可能有助于你跟上进度。它大体上遵循 Jarred 的方法论,在每个阶段都有审查和关卡。Mike 使用了类似的整体结构,采用类似的循环工作流,但他端到端地运行了整个迁移,根据结果修订了规则和工作流,然后再次运行——每次都丢弃输出,直到第三次运行。

第 1 步——创建规则手册、依赖关系图和缺口清单

在这个阶段,我们正在为迁移奠定基础:一份需要重构而非仅仅翻译的代码位置清单、一本关于如何翻译代码的规则手册,以及一张用于排列迁移实施工作流顺序的依赖关系图。

顺序很重要:规则手册必须先于缺口清单。缺口清单是由规则手册的默认规则无法覆盖的内容所定义的,二者会在一次联合审计中一起接受检验。

规则手册

规则手册的确切形态取决于你必须在开始时做出的关键架构决策。其中首要的是:新代码是沿用相同的结构,还是会被彻底重新设计。

如果是前者(Jarred),规则手册将主要是查找表,用于在语言之间转换类型和惯用法,同时指向缺口清单以处理那些更难转换的组件。如果是后者(Mike),它就会是一份设计文档。

Jarred 通过与 Claude 对话来创建他的规则手册,为每一处歧义领域制定一条策略。他还使用了八个子智能体,专门基于他自己的直觉,针对 8 种不同类别的常见失败模式进行审查。

依赖关系图

你需要理解文件依赖关系,才能有效地为并行迁移拆分工作流,从而知道哪些文件应先迁移、哪些文件应归入同一批次。有些语言和代码库有显式的清单文件,使这件事变得容易,但对于遗留代码库以及 C/C++ 和 Python 等许多流行语言来说,这些依赖关系需要被发现并绘制出来。

Claude Code 可以部署智能体来创建并运行一个确定性脚本,以生成这张映射图。迁移工具包中的提示词使用一套工作流来创建审查与修复循环。注意:该入门工具包是本文所述流程的通用模板——它并非这些具体移植项目所实际使用的版本。

差距清单与怀疑派审查者

新语言有着不同于旧语言的要求,这些要求必须得到满足。对于 Zig 到 Rust 而言,差异在于手动内存管理(C 和 C++ 也是同样的方式)。例如:

Zig

fn readConfig(allocator: std.mem.Allocator) ![]u8 {
    const buf = try allocator.alloc(u8, 1024);
    // ...fill buf...
    return buf; // caller must free this — but only the comment says so
}
// A caller that forgets 'defer allocator.free(buf)' still compiles — the leak only surfaces at runtime.

Rust

fnread_config() -> Vec<u8> { 
let buf = vec![0u8; 1024]; 
// ...fill buf... buf // ownership moves to the caller; memory is freed automatically } 
// Use it after it's moved? Free it twice? Neither compiles. // Forget to free it? There's no free call to forget — drop is automatic.

对于 Python 到 TypeScript 而言,差距在于接口与契约。Python 不要求声明它将接受什么形状的对象或返回什么,但 TypeScript 要求。例如:

Python

def(handler):    handler.setup()
return handler.run({"retries": 3})
# Any object with .setup() and .run() works here. Which objects actually get passed in? Read the whole codebase to find out.

TypeScript

interface RunResult { ok: boolean } 
interface Handler 
{ setup(): void; 
run(opts: { retries: number }): Promise<RunResult>; 
} 
function(handler: Handler): Promise<RunResult> { 
handler.setup(); 
return handler.run({ retries: 3 }); } 
// The contract must be written down before this compiles

Jarred 和 Mike 都创建了差距清单文件来记录这些隐性知识。Jarred 在一开始就清点了这些差距,这也是我们在这里的做法,而 Mike 选择先翻译,然后再通过事后审计来创建差距清单。你可能两种方式都需要做。

来看看这个用于创建差距清单文件的Claude Code 提示词示例。

第 2 步——对规则进行压力测试

这一步涉及一次小型迁移,作为更大规模迁移的“试航”。

在这一步中,Jarred 使用一个智能体依据规则手册翻译三个文件,一个智能体“像资深 Rust 工程师那样”翻译三个文件,还有一个智能体利用 diff 来创建新的翻译规则。在这个阶段,他发现了两个关键问题,如果这些问题扩散到全部 1,448 个文件,将会造成大量问题。

提示词可能看起来像这样。

这种压力测试仅适用于保持结构的迁移,即同一文件的两种翻译可以逐行比较。如果你的规则手册是一次重新设计——就像 Mike 的那样——那么等效的测试就是用对抗性审查者直接攻击设计文档,然后通过一次一次性的端到端运行来验证它。

无论如何,把任何已翻译的文件都扔掉。目标是完善规则,而不是取得渐进式进展。

第 3 步——翻译所有内容

对于剩余的步骤,你运行相同的多智能体循环架构:实现、审查和修复。

你可以把实现者的工作下放给更小的模型,而让审查者继续使用更大的模型。例如,Mike 在为主迁移任务展开 12 个子智能体时,使用的是 Claude Sonnet。

工作队列应当是机械化的。一个批处理脚本通过检查翻译后的文件是否存在于磁盘上来判断哪些已完成,然后把待处理的文件切成批次,交给实现者智能体。由于队列每次都是从磁盘重建的,这次迁移在构造上就是可恢复的。

在这个阶段,智能体可能会对自己该做多少工作过于谨慎。解决办法可以是一条直截了当、语气强烈的提示词指令,并附上背景说明:编译器会在下一步捕获错误。

翻译器无法自信执行的任何内容,都会被标记为 // TODO(port): <reason>,留待第 4 步处理。从这里开始,待办清单会自己写出来:编译器枚举错误,冒烟测试找出崩溃,测试套件报告失败。

两个对抗性审查者使用各自独立的上下文来评估实现者的工作,审查者之间的分歧会交给第三个智能体。当某个审查者在多个文件中反复发现同一个错误时,解决办法不是逐文件修复。你往规则手册里加一句话,然后重新生成受影响的批次。规则手册在这一步中不断扩充;代码永远不会针对它做手工修补。

这一步中需要注意的一个重要设计决策是编译器所处的位置。Mike 在每一轮循环中都运行 TypeScript 编译器,因为它检查一个单元只需几秒。Jarred 则完全禁止编译器进入循环,把它推迟到下一步,因为 cargo 要花好几分钟。

到了这一步,大部分繁重的工作已经完成,提示词开始变得越来越短。

第 4、5、6 步——编译、运行并匹配行为

这三个步骤共享相同的循环架构,且所需的人工判断逐步减少,因此我们将它们放在一起讨论。

例如,第 4 步往往会融入第 3 步,具体取决于迁移所用的语言和规模。

取决于编译步骤的规模和难度,智能体可能完全不会执行这一步。Jarred 用一个编排脚本执行了这一步,该脚本在整个工作区范围内调用了一次编译器。随后“修复智能体”并行地遍历错误列表,同时进行对抗性审查。再次运行构建,如此反复。

审查错误列表有助于发现可能需要调整的系统性问题。例如,Jarred 遇到了数千个 Rust 模块错误,这些错误是在修复了 Zig 惰性编译所容忍的循环导入之后才浮现出来的。他通过编码逻辑来对依赖进行分类——判断哪些依赖需要删除、移动或重构边界——从而修复了这个循环。

第 5 步同样有一个类似于编译器错误列表的机械化事实来源:冒烟测试中的崩溃。同样,循环修复的方法是将问题分组归类,在这里是按根因对原因进行分组,再由对抗性子智能体进行审查。

第 6 步,也是我们故事的结尾,是比较这两个代码库中程序的行为。

我们的文件现在已经被翻译、编译并做了冒烟测试。现在是时候对它们进行分片,并针对它们运行(来自前置阶段的)测试套件了。用“修复智能体”来应对失败,这些智能体会对照两个代码库审查失败的测试。对抗性审查者会检查它们的修复。

这个循环中的下一个阶段是一个构建守护进程,它是唯一被允许重新构建二进制文件的进程。修复者编写补丁;守护进程将它们批量处理,一次性重新构建,重新运行受影响的测试,并把结果反馈回来。这样就把最昂贵的操作串行化了,而不是让多个智能体各自独立触发它。

当同一个失败在许多测试中反复出现时,修复就要上移:你修改产生该 bug 的规则,并且只重新生成该规则所触及的文件。

Mike 的方法在这里很重要,因为许多开发者不会有已经构建好或移植好的测试套件。Mike 让 Claude 创建了一个小脚本,针对新的移植版本和原始的 Python 代码库运行 7 个真实世界场景,并对结果进行 diff。每个失败的场景都有自己的修复智能体,这个循环一直运行到全部 7 个都通过为止。

然后他又更进一步。Claude 设计了自己的端到端测试套件,并在夜间自主运行,修复出错的地方并连续四个晚上重新运行。结果,它捕捉到了任何场景清单都无法预料到的那些小毛病。

教训在于,缺少测试套件并不会阻碍这一步。如果你无法继承一个裁判,就让 Claude 构建一个。无论哪种方式,你原有的代码库都是基准真相。

代码迁移最佳实践

每一次运行都教会了我们一些上一次没有的东西。可以很有把握地说,你的下一次迁移会教给你这本指南无法涵盖的东西。但有一些实践在每一个项目中都经受住了考验:

  • 不要盲目遵循这本指南。 每次迁移都各不相同。把这当作一个起点,在确定具体迁移方案之前,先和 Claude 一起规划。
  • 不要关注个别失败。 个别失败是循环的职责。修复型智能体会把它们逐一消除。你的注意力应该放在模式上。
  • 让审查具有对抗性,让验证机械化。 对抗性审查允许更长时间运行的任务,通常值得消耗这些 token。让脚本——编译器、diff、测试套件——来充当裁判。
  • 不要对所有任务都使用最大的模型。 Token 消耗集中在你的循环中,所以要有意地设计它们。较小的模型能很好地处理高并发的实现扇出;把你最大的模型留给审查者,以及任何会编写其他智能体将要遵循的规则的任务。
  • 把人力投入前置。规则手册和压力测试是最耗时的部分。之后的一切基本上就是排队消耗任务。
  • 让工作队列机械化且可恢复。“完成”应当意味着“输出文件已存在于磁盘上”。

审查循环结果,而非代码

Jarred 的 Bun 迁移现已投入生产,尽管每次迁移都有取舍。例如,大约 4% 的 Rust 代码位于“unsafe”块中,大多是 C/C++ 边界处的单行指针操作。

但新的代码库可衡量地更好了。团队工具能检测到的每一处内存泄漏都已修复:一项 2,000 次重复构建的基准测试从 6,745 MB 内存降至 609。二进制文件在 Linux 和 Windows 上小了 19%。跨语言优化使其在 HTTP 服务和 next build、tsc 等真实工作负载中快了 2–5%。

考虑一下是否该重新算一算你长期搁置的迁移这笔账。挑一个你一直将就着的代码库,问问 Claude 它的迁移流程会是什么样。

来源:Claude:Blog(网页) · claude.com