前些年,生产级代码库的跨语言迁移不仅要花上数年时间编写代码,还要长期维护两套并行的语言实现。然而最近一个月,借助 Claude Code 和多个 Claude 模型,Anthropic 多名研发人员完成了 10 个代码包的语言迁移,规模从数万行覆盖到百万行级别。
其中,比较知名的迁移案例是 Bun 联合创始人、Anthropic 技术团队成员 Jarred Sumner 用了 11 天,把 Bun 的约 100 万行代码从 Zig 迁移到 Rust,迁移文章见 Bun 重写到 Rust,Claude 跑出的大迁移。经过分批迁移、编译修复和测试收敛后,迁移分支通过了 Bun 既有测试套件的 CI 检查,最终合并进主分支。这次重写共引入 19 个已知回归问题,目前均已修复。同时 Rust 版 Bun 作为底层运行时,用于 6 月 17 日发布的 Claude Code v2.1.181 及后续版本。
无独有偶,Anthropic Labs 联合负责人 Mike Krieger 利用一个周末,把一套 Python 代码库迁移成约 16.5 万行 TypeScript。整个迁移过程中,他用了数百个 Agent 分阶段推进迁移,前后设置了八次验收,关键结果再经过三轮对抗式审查。最后,团队逐条运行原有命令,对比 Python 与 TypeScript 版本的输出,确认迁移后的行为保持一致。
基于这些迁移实践,Anthropic 总结出了一套由规则、任务队列和机械化验证组成的六步迁移流程。
语言迁移的时机与成本
一般来说团队决定迁移语言,大概率是项目环境发生了变化。早期可以接受的技术取舍逐渐成为瓶颈,出现了更合适的实现路径,或是原有语言和生态正在收缩。
以 Bun 的迁移为例,Jarred 最初选择 Zig,是因为它兼顾接近 C 的性能和较低的语言复杂度,适合在没有大模型协助的情况下一个人快速完成 Bun 的初始版本。但随着 Bun 的规模、用户量和稳定性要求持续上升,手动管理内存带来的维护负担也越来越明显。
Bun CLI 每月下载量超过千万,Claude Code 等工具也将 Bun 作为底层运行时。放在前些年,即使重写需求十分的迫切,研发团队也很难冻结产品路线图,再投入数个季度完成跨语言迁移。现在,团队可以先在隔离分支中反复运行整套迁移流程;结果不理想时,直接丢弃产物、修改规则,再从头执行。
虽然 AI 明显降低了迁移成本,但是技术团队仍然要先回答一个问题:这次迁移究竟能带来什么业务价值。毕竟跨语言迁移依然是一项昂贵的工程。百万行级别的项目如今未必要花四年时间,投入 300 万至 400 万美元,但实际执行成本仍可能达到数万至数十万美元。像是 Bun 的迁移就消耗了 59 亿个未缓存输入 Token 和 6.9 亿个输出 Token,按 API 价格估算约为 16.5 万美元;Mike 的迁移在主要阶段,也消耗了约 2,700 万个 Token。

图 1:Jarred 提交的百万行迁移 PR
虽然成本摆在那里,但迁移的理由也不用达到“项目无法继续”的程度。持续一年修复内存问题的记录,或是一个长期存在的构建瓶颈,都可以支撑起迁移决策。Mike 的项目就是由编译环节推动的:内部工具需要以单个二进制文件交付,但 Python 工具链大约要花 8 分钟在单个平台的构建上,完成所有平台的构建则要等待约 30 分钟。Mike 的项目迁移完成后,同一编译过程缩短到约 2 秒,二进制启动速度提高到原来的 6 倍,团队还停止了一条独立的部署流水线的维护工作。
AI 辅助迁移带来的变化主要体现在一次迁移失败要付出的代价上。之前,迁移路线一旦出现问题,可能要推倒重来大量人工编写的代码和协调投入。现在,研发团队可以保留整理好的规则、脚本和验证流程,只丢弃这一轮生成的代码,修改规则后重新运行。
因此,团队的评估重点慢慢变成了能否建立一套可以重复执行、并通过自动化检查验证结果的迁移流程。
AI 为什么适合大规模代码迁移
Claude Fable 5 和 Claude Opus 4.8 这两个新模型擅长把大型目标拆解成多个并行工作流,并通过 Subagent 完成委派、执行和验证。这和大规模代码的迁移工作有很多匹配点:
- 工作可以并行拆分。 文件、模块、crate 或子系统都可以成为相对独立的迁移单元,方便数十到数千个 Agent 任务同时推进任务。
- 旧代码提供了完整上下文。 原实现本身就是一份可执行规格,模型可以直接读取类型、控制流、边界条件和现有行为。
- 代码库通常自带裁判。 编译器、测试套件、静态检查和输出 diff 都可以用于判断迁移结果是否正确。
- 失败会自动形成任务队列。 编译错误、测试失败和行为差异都能直接转化为下一轮 Agent 的输入。
- 规则可以统一边界行为。 审查 Agent 会指出每个问题违反了哪条迁移规则,重复出现的错误可以回写到规则手册,减少后续任务的继续偏离。
AI 能够持续推进大型迁移,除了需要能批量生成代码的模型之外,还依赖一套清晰、可拆分、可验证的任务系统。每项任务都要有明确输入,执行单元要彼此独立,结果也得能通过编译或测试自动判断。迁移任务队列还可以根据磁盘上的文件状态、编译错误和测试结果重新生成,让 Agent 持续领取尚未完成的任务。即使 Agent 的迁移任务运行中断,也能从已有进度继续执行。
6 步迁移流程
在正式开始 6 个迁移步骤前,研发团队要先建立一套足够可靠的验收体系,也就是整个迁移过程的“裁判”。如果缺少这套体系,迁移流程就无法判断何时结束,也无法确认新版本是否真的达到了预设的验收标准。
搭建这套验收系统,一般包括 3 项工作。首先,团队要梳理并分类现有测试,区分哪些测试能够通过 CLI、API 或其他外部接口直接运行,哪些测试依赖旧语言的内部实现,迁移后会无法继续使用。其次,团队需要把能够验证外部行为的测试改写为同一组断言,使这些断言既能用于旧实现,也能用于新实现。随后,由专门负责挑错的 Agent 审查改写结果,确认测试没有在改写过程中降低原有要求。最后,团队要先用原有代码运行整套验收体系,确认所有检查能够通过。再通过故意破坏部分代码,检查这套体系能否准确发现错误。一套无法识别明显问题的测试系统,绝对不能承担最终的验收工作。
以上面 Bun 的迁移为例,Jarred 在迁移的每个阶段都设置了审查和验收关卡。同样的,Mike 的整体验收流程和 Jarred 类似,但采用了更激进的策略:团队先完整执行一轮端到端迁移,再根据这一轮暴露的问题修改迁移规则和工作流。如果验收有问题,就丢弃这一轮生成的全部代码,从头重新执行。前两轮主要用于检验和修正迁移流程,直到第三轮,团队才保留生成结果并继续完成后续验收。

图 2:大规模代码迁移六步流程
规则手册、依赖图和差距清单
正式批量翻译代码前,第 1 个阶段要先建立规则手册、依赖图和差距清单。这个 3 个前置准备共同决定了后续任务如何拆分、按什么顺序执行,以及 Agent 遇到特殊情况时应该如何处理。
规则手册、依赖图和差距清单的建立顺序也很重要:团队要先确定通用迁移规则,再梳理这些规则无法覆盖的例外情况,最后再把规则手册和差距清单放在一起审查,确认两者能够完整覆盖整个代码库。

图 3:规则手册、依赖图和差距清单
规则手册的具体形式,取决于团队是否准备在迁移过程中调整原有架构。 如果新版本基本上保留了原来的代码结构,规则手册就会像是一份语言映射表,记录两种语言之间的类型转换、惯用写法和语义对应关系。一旦遇到无法直接转换的内容,可以交给差距清单单独记录和处理。Jarred 对 Bun 的迁移就采用了这种方式。
如果新版本调整了架构,规则手册就要承担更多设计工作。除了语言之间的转换规则,它还要明确新系统的模块边界、接口定义和目标结构,让 Agent 知道每段旧代码在新架构中应该放在哪。Mike 的项目就采用了这条路线,因此他的规则手册会更像一份完整的架构设计文档。
Bun 的迁移,是通过与 Claude 持续对话,逐步补全迁移规则,并为每个存在歧义的领域确定统一处理方式。Jarred 还为迁移设计了八个专门的 Subagent,让它们分别检查八类常见的迁移错误。要判断某个问题是否应该写进规则手册时,可以采用一个很直接的标准:只要两个 Agent 面对同一个转换问题时可能给出不同答案,团队就应该提前确定唯一方案,并把它写进规则手册,避免后续任务反复作出不同判断。
依赖图负责确定文件的迁移顺序和并行批次。 迁移系统需要提前知道哪些文件必须先处理,哪些文件可以放在同一批并行执行,以及哪些位置可能形成循环依赖。Claude Code 可以调度 Agent 编写确定性脚本,直接扫描代码并生成依赖关系。能够通过脚本计算的结果,应尽量交给脚本处理,避免模型仅凭文件名称或代码语义猜测依赖关系。
依赖分析不能只停留在文件层面。研发团队要检查目标语言中的包、模块或 crate 之间如何组织的。即使文件级依赖图看起来没有问题,合并进更大的包级结构后,仍然有可能会出现大量循环依赖和编译错误。因此,文件粒度和模块粒度的依赖关系都要在正式迁移前确认。
差距清单记录的是两种语言之间无法通过通用规则直接转换的信息。源语言可能允许某些知识隐藏在运行时行为、动态类型或内存管理方式中,目标语言则要求开发者把这些信息明确写出来。Zig 迁移到 Rust 时,差距主要集中在所有权、生命周期和内存释放方式;Python 迁移到 TypeScript 时,重点则落在接口定义、返回值类型和对象结构约束上。
下面,用一个示例来看下这类差距在实际代码中如何出现的:
fn loadConfig(allocator: std.mem.Allocator) ![]u8 {
const data = try allocator.alloc(u8, 1024);
// 填充数据
return data; // 调用方需要记得释放
}
fn load_config() -> Vec<u8> {
let data = vec![0u8; 1024];
// 填充数据
data // 所有权转移,离开作用域后自动释放
}
在 Zig 示例中,调用方遗漏释放逻辑时仍可能成功编译,问题通常要到运行时才暴露;Rust 会通过所有权和类型系统提前限制重复释放、移动后继续使用等情况。差距清单需要把这类隐含知识转化成可搜索的条目,供实现 Agent 在处理具体文件时查询。Jarred 选择在迁移前建立清单,Mike 则先完成翻译,再通过审计补齐清单,实际项目中可能需要同时使用两种方式。
规则的压力测试

图 4:规则压力测试
Bun 的迁移在压力测试环节安排了 3 条独立路线:第一个 Agent 严格依据规则手册翻译 3 个文件,第二个 Agent 以“资深 Rust 工程师”的方式独立翻译同样的文件,第三个 Agent 负责比较两组结果,并根据 diff 补充迁移规则。这个小规模测试提前发现了两个关键问题,由此判断:如果直接把迁移任务扩展到全部 1,448 个文件,同类错误就会被批量写入迁移后的 Rust 代码中。
这种“双翻译再对比”的方法,比较适合保留原有结构的迁移,因为可以直接对同一个文件的两份实现比较当中的差异点。如果规则手册同时包含架构重构,文件之间就难以逐行对应,diff 的参考价值就会明显下降。此时,更推荐让负责挑错的 Agent 直接审查设计文档,再完整跑一轮可随时丢弃的端到端迁移,检查这套设计能否真正执行下去。
无论采用哪种方式,这一阶段生成的代码都不应保留。它的任务是暴露规则和流程中的问题,为后续正式迁移校准方向,而不是提前积累迁移进度。
迁移全部代码

图 5:多 Agent 批量迁移代码
从这个阶段开始,后续步骤基本上会沿用同一套多 Agent 循环:先实现代码,再进行独立审查,最后根据审查结果修复问题。高吞吐的实现任务可以交给较小模型,大模型则负责审查,以及修改规则等会影响其他 Agent 的关键工作。像 Mike 在主要迁移阶段就用 Claude Sonnet 模型,并行调度 12 个 Subagent 处理不同批次。
迁移任务队列应尽量由脚本自动维护。脚本通过检查目标文件是否存在,来判断哪些迁移单元已经完成,再把剩余文件划分成新批次交给实现 Agent。由于队列每次都要根据磁盘状态重新生成,迁移流程可以随时暂停和恢复。Agent 无法确定如何迁移的部分,则统一标记为 TODO(port): <reason>,留待后续处理。
每个迁移单元由两个上下文独立的审查 Agent 进行检查,意见冲突的话就交给第三个 Agent 来裁决。如果同类错误反复出现,团队就修改规则手册,并重新生成受影响的批次,避免围绕错误代码逐个打补丁。
在循环中编译器的位置取决于执行成本。TypeScript 的编译只要数秒,它就可以在每个单元内运行;Rust 工作区编译要数分钟,统一留到下一阶段进行。总之,秉持一个原则“廉价检查高频运行,昂贵检查集中处理”。
编译、运行与行为匹配

图 6:编译、运行与行为匹配
后面的 3 个阶段会采用相似的循环结构,而且随着流程推进,需要人类判断的环节会越来越少。第 4 个阶段是编译阶段,由编排脚本统一编译整个工作区,把编译错误整理成机器可读的任务队列,再交给多个修复 Agent 并行处理。Agent 完成修复后,编排脚本会重新构建整个工作区,生成下一轮错误队列,如此循环,直到编译通过。
要定期审查错误队列,如果存在大量的重复错误说明迁移流程本身有问题。Jarred 在处理了 Zig 延迟编译机制能够容忍、但 Rust 无法接受的循环导入后,遇到了数千个 Rust 模块错误。因此,Bun 团队调整了迁移流程,加入依赖分类逻辑来判断每条依赖应该删除、移动,还是通过重新划分模块边界解决。第 5 个阶段的烟雾测试沿用同样的思路:先按根因归类崩溃和失败,再交给对抗式 Subagent 复核。
第 6 个阶段用来比较新旧代码库的外部行为。到这一步,迁移后的代码已经完成全部的翻译工作,并通过了编译和基础运行检查,接下来就是交由前期建立的测试体系继续验证。每个失败测试都会分配给一个修复 Agent,由它同时检查新旧实现,定位行为差异并提交补丁,再由对抗式审查 Agent 复核修复结果。
迁移工具包还设有构建守护进程,只有它可以重新构建二进制文件。修复 Agent 只负责提交补丁,守护进程负责汇总变更、统一构建、重新运行受影响的测试,再把结果写回迁移任务队列。这样可以将最昂贵的构建操作串行执行,避免多个 Agent 重复构建并相互干扰。
由于很多项目缺少完整、可直接沿用的测试套件,Mike 采用了另一种验证方式。他先让 Claude 编写一个小型脚本,在新旧代码库中分别运行 7 个真实使用场景,并对比两边的输出。每个未通过的场景都会交给独立的修复 Agent,直到 7 个场景全部通过。随后,Claude 又自行设计了一套端到端测试,并连续 4 个夜晚运行测试、修复问题和重新验证,进一步发现预设场景没有覆盖的问题。
缺少现成测试不会让语言迁移无法推进,但研发团队还是得补建一套能够验证外部行为的“裁判”。旧代码库可以继续当可执行规格,用来对比新版本的输出、报错、退出码、文件变化和性能边界。团队要先确认这套裁判能够准确识别差异,再根据它发现的问题持续生成修复任务。
大规模迁移的实践原则
每次迁移都会暴露新的问题,但 Anthropic 在多个项目中逐渐总结出 5 个相对稳妥的做法:
- 先根据代码库制定迁移方案。 上面的 6 步迁移流程可以作为起点,但正式投入前,仍要让 Claude 分析源语言与目标语言的差距、架构目标、测试条件和验证成本。评估结果也要允许团队得出“暂时不迁移”的结论。
- 优先处理重复出现的问题。 单个失败可以交给修复 Agent,人类则应把时间集中在反复出现的错误、规则缺口和任务队列设计上。
- 结合对抗式审查与机械化验证。 审查 Agent 应在独立上下文中主动寻找问题,最终结果要尽量交给编译器、测试套件、静态检查和输出 diff 判断。
- 根据任务分配模型。 较小模型适合承担大量实现工作,更强模型则用于审查、规则设计,以及处理会影响其他 Agent 的关键决策。
- 把人类判断集中在前期。 规则手册和压力测试需要投入最多人工判断,后续阶段主要围绕编译、运行和测试生成的任务队列持续推进。
这些做法也形成了一套更清晰的工程分工:人类负责划定边界、设计验收体系、识别系统性偏差并调整流程,Agent 则承担大规模、重复性的实现和修复任务。随着迁移推进,人类的注意力会逐渐从单个文件转向整个流程:迁移规则是否仍然有效,验证结果是否可信,以及流水线是否还在批量复制同类错误。
用可测量结果评估迁移质量
已经进入生产环境的 Bun Rust 迁移中有些取舍。例如,约 4% 的 Rust 代码位于 unsafe 块中,其中大部分用于处理与 C/C++ 边界交互时的单行指针操作。同时,新代码库在内存占用、二进制体积和运行性能等多项指标上取得了可测量的改善。
现有工具能够检测到的内存泄漏已经全部修复。在一项连续执行 2,000 次构建的基准测试中,内存占用从 6,745 MB 降至 609 MB;Linux 和 Windows 平台的二进制体积缩小了 19%;经过跨语言优化,HTTP 服务以及 next build、tsc 等真实工作负载的运行速度提升了约 2% 至 5%。这些结果表明,迁移质量最终要通过行为一致性、稳定性、资源消耗和性能数据来衡量,生成了多少行代码只能说明迁移规模。
这套方法把大型迁移变成了一套可以反复运行、持续修正的工程流程:规则手册统一实现,依赖图安排顺序,差距清单记录例外,任务队列、独立审查和编译测试负责推进与校验。每一轮都会生成新的代码,最终质量取决于规则是否完善、验证是否可靠,以及反馈能否及时修正偏差。
在 云栈社区 ,我们持续追踪 AI 辅助开发的最新实践,期待这套可复现的迁移方法论能给你带来启发。
相关资料