EN
方法论 · Claude Code · 2026-07-16

大规模代码迁移的六个阶段

核心不是改代码。代码错误只是表面结果,真正要修的,是那个持续产出错误代码的循环。

一手来源
How Anthropic runs large-scale code migrations(官方,claude.com/blog)
两个真实案例
Bun 一百万行 Zig → Rust(两周) · 内部工具 Python → 16.5 万行 TypeScript(一个周末)
本文回答
六个阶段各自在做什么 · 为什么它们是同一个循环 · 两种迁移路线怎么分岔
— 全文要点

迁移的风险结构变了:失败的代价,从数年与半成品,变成 token 与几天时间

立项门槛

过去必须有生死攸关的理由。现在一个长期的内存问题、一个拖慢交付的构建瓶颈,就足以启动一次迁移

核心方法

把系统看成一条装配线:小模型负责实现,大模型负责挑错,编译器与测试负责判定。人只设计装配线、编写规则。

开局选择

保留旧结构,还是同时重新设计?这个选择决定了规则手册、编译器位置和整条流水线的走法

§I
为什么突然变得可行

迁移的门槛,塌了

把生产系统从一种语言重写成另一种语言,过去通常意味着一场持续数年的高风险工程。

难点不只是代码量大。迁移期间,团队要同时维护新旧两套实现。产品路线图会被拖慢,关键工程师长期被占用。几年之后,新系统还可能只有九成行为与旧系统一致,而剩下的一成,往往比最初的问题更难处理。因此,很多团队宁愿继续忍受内存错误、构建缓慢和技术债,也不愿押注一次大规模重写。

过去一个月,Anthropic 的工程师用 Claude Code 迁移了十个代码包,规模从几万行到几十万行不等。其中两个项目尤其有代表性。

Jarred Sumner 把 Bun 的核心实现从 Zig 迁到了 Rust。两周内生成约一百万行代码,涉及 1448 个文件,合并前 Bun 原有测试在持续集成环境中全部通过,上线后发现的 19 个回归也已修复。Mike Krieger 则在一个周末内,把一个 Python 代码库重写成约 16.5 万行 TypeScript,过程中使用了上百个 agent(能够独立领取任务、修改代码和验证结果的模型执行单元)。

过去的代价
四年 · 300–400 万美元
一百万行迁移的典型人力投入,还要承担「养两套代码、可能收不了尾」的风险。
现在的代价
两周 · 约 16.5 万美元
Bun 这次的 API 费用(59 亿未缓存输入 token + 6.9 亿输出 token)。路线不对,最坏只是删掉分支重来。

成本并没有消失,只是风险结构发生了变化。这使迁移的立项标准明显降低:过去必须有生死攸关的理由,现在一个长期存在的内存问题,或者一个持续拖慢交付的构建瓶颈,就可能足以启动。Mike 的项目就是因为编译太慢——原来的 Python 工具链在每个平台上需要约八分钟,完整发布矩阵要等待三十分钟;迁到 TypeScript 后,同样的编译约两秒完成,二进制启动速度提高六倍,还停掉了一条单独维护的部署流水线。

§II
整套方法的出发点

要改的不是代码,是产出代码的流程

传统迁移很容易被理解成翻译工作:打开一个文件,把旧语言改写成新语言,遇到错误再逐个修正。

这种方法在几千行代码上或许可行。到了上千文件、上百万行的规模,人已经无法逐项监督。真正可扩展的做法,是把注意力从代码移到生产代码的流程上。

可以把整个系统看成一条装配线:小模型负责大量实现,大模型负责挑错,编译器、测试和输出差异负责判定结果。人不再逐个修改文件,而是设计装配线、编写规则手册,并观察哪些错误反复出现。如果同一种问题出现在几十个文件里,正确动作不是修几十次,而应该修改规则手册中对应的规则,然后重新生成受影响的文件。

代码错误只是表面结果。真正需要修正的,是那个持续产出错误代码的循环。

Anthropic 官方 · 2026-07-16
§III
开工前的唯一前提

先造出一个能判分的裁判

自动化迁移要连续运行几天,首先必须回答一个问题:机器凭什么知道任务已经完成?

如果没有明确的判定标准,循环就无法停止。模型可以不断生成看似合理的代码,却没有办法证明新系统与旧系统行为一致。这个判定系统必须能用同一套标准检查旧代码和新代码,而困难在于,很多旧项目的测试依赖内部函数、私有类型或语言特有机制,无法直接搬到另一种语言。

造裁判的三步

第一步,分类。让 Claude 判断现有测试里,哪些表达的是外部可观察行为,哪些绑定了内部实现。

第二步,改写。把外部行为改写成新旧系统都能运行的断言,再安排对抗式 agent 检查,确保改写过程中没有偷偷降低标准。

第三步,验证裁判本身。正确的旧代码必须通过,故意破坏的旧代码必须失败。一套抓不住人为错误的测试,没有资格担任迁移裁判。

两个案例在这里就分了岔。Bun 的条件比较理想,它原有的测试主要由 TypeScript 编写,不依赖 Zig 或 Rust 的内部结构,同一套测试可以原封不动地检查迁移后的实现。Mike 的项目没有这种条件,他为此建立了一套行为一致性框架,选取七个真实使用场景,分别运行 Python 版和 TypeScript 版,再逐项比较输出,任何差异都被视为缺陷。

多数项目更接近后一种情况。没有现成的跨语言测试并不意味着不能迁移,而是要先让 Claude 帮助建立一套。旧系统仍然是最终标尺。

§IV
看懂这一点,后面就不难

六个阶段,其实是同一个循环

Anthropic 把迁移分成六个阶段,但它们并不是六套独立方法。整个过程始终使用同一条流水线,只是每一轮更换不同的裁判。

脚本先扫描磁盘,找出尚未完成的任务,再把任务切成批次放入队列。多个小模型并行实现,两个大模型从不同角度进行对抗式评审,如果意见不一致,再交给第三个 agent 裁决。最后,由编译器、测试或输出差异判定结果。通过的任务进入下一阶段,失败的任务重新回到队列。

一轮流水线的样子(六个阶段共用)
队列
派发任务
脚本扫描磁盘,找出未完成的,切成批次
实现
小模型并行
承担高吞吐的代码生成
评审
大模型对抗
两个从不同角度挑错,分歧交第三个裁决
裁判
交给工具
编译器 / 测试 / 输出差异,不靠人
通过 → 进入下一阶段,或标记完成(在磁盘上留下输出文件)
失败 → 回到队列;同一种问题反复出现,就修改规则、重新生成那一批
两条规则:生成的代码不接受人工补丁 · 完成状态落在磁盘上,所以流程可随时中断、从断点继续

六个阶段中,真正需要人投入大量判断的主要是前两个。后面的工作,更多是在消耗队列和计算资源。

§V
走一遍六个阶段

前两步靠判断,后四步靠队列

1
建立规则、依赖和缺口
迁移前准备三样东西,顺序很重要。规则手册是所有翻译 agent 共同遵守的工作说明(保留结构时是类型与惯用写法的对照表,重新设计时更接近一份完整设计文档,说明模块边界、接口契约、错误模型和数据流)。依赖图告诉流水线哪些文件先处理、哪些可以并行。缺口清单记录新语言强制要求、而旧语言没有明确表达的东西。规则手册必须先于缺口清单,因为所谓「缺口」,本质上就是规则手册默认覆盖不到的部分。
2
用一次小迁移压测规则
正式展开之前,先挑少量文件做试航。在保留旧结构的迁移中,让一个 agent 严格按规则手册翻译,另一个 agent 按资深工程师的判断独立实现,第三个 agent 比较两版差异,反推规则手册遗漏了什么。Jarred 在这次三文件试验中,发现了两个会影响全部 1448 个文件的严重问题。试验生成的代码随后全部丢弃——这个阶段的目的不是积累进度,而是尽早暴露会被批量复制的错误。
3
展开全量生成
规则稳定后,小模型开始大规模并行生成(Mike 在主迁移阶段同时运行了 12 个 Sonnet 子 agent)。遇到暂时无法处理的部分,agent 留下明确的迁移待办标记,交给后续阶段。如果评审持续发现同一种错误,流水线就在规则手册中增加一条规则并重新生成相关批次;规则手册只增加约束、不随意改写旧规则,代码本身仍然不接受手工修补。 裁判:评审 agent(编译器是否入循环见下节)
4
让编译器消化结构问题
裁判变成编译错误列表。多个修错 agent 并行处理,再由对抗式评审检查修复。这里不能只看单个报错,更重要的是识别系统性原因:Bun 的旧 Zig 代码依赖惰性编译、容忍循环导入,Rust 不接受同样的模块结构,Jarred 处理完循环依赖后一次暴露出数千个模块错误,真正的解决办法是建立一套分类规则(什么依赖该删除、什么定义该移动、什么循环说明边界要重构)。 裁判:编译错误列表
5
先让系统跑起来
代码能够编译,不代表程序能够运行。这一阶段用冒烟测试捕获启动失败、崩溃和关键路径异常,按根因分组后交给不同的对抗式子 agent 处理。随着裁判变得更机械,人需要参与的判断越来越少,很多项目甚至会把编译和运行检查直接并入全量生成阶段。 裁判:冒烟测试
6
证明新旧行为一致
最后一轮的裁判,是开工前建立的跨语言测试或行为一致性框架。流水线把测试分片交给不同 agent。为避免多个 agent 同时触发昂贵的全量构建,设置一个构建守护进程:只有它有权重新编译二进制,其他 agent 只提交修改,守护进程积累一批后统一构建、运行受影响的测试、把结果送回队列。这样,最昂贵的操作保持串行,便宜的分析和修复仍然并行。 裁判:跨语言测试 / 逐项 diff
「缺口」是什么:两个例子

缺口清单记录的,是新语言强制要求、旧语言却没有明确表达的东西。下面两段代码能说清这个概念。

Zig旧 · 内存靠人管
fn readConfig(a: Allocator) ![]u8 {
    const buf = try a.alloc(u8, 1024);
    return buf; // 谁来释放,只写在注释里
}
// 调用方忘记释放,依然能编译,
// 问题要到运行时才暴露。
Rust新 · 编译器强制
fn read_config() -> Vec<u8> {
    let buf = vec![0u8; 1024];
    buf // 所有权转移,自动释放
}
// 用了已转移的值、重复释放、
// 生命周期不成立,都编译不过。

Zig → Rust,缺口是内存所有权。Zig 靠人记着释放,Rust 要求所有权关系能被编译器证明。这类隐含知识必须先写进缺口清单。

Python旧 · 不声明契约
def register(handler):
    handler.setup()
    return handler.run({"retries": 3})
# 任何带 setup/run 的对象都行,
# 真实传入了什么,要搜遍全库才知道。
TypeScript新 · 先声明契约
interface Handler {
  setup(): void;
  run(o: {retries: number}): Promise<Result>;
}
function register(h: Handler) { ... }
// 契约不写下来,无法通过类型检查。

Python → TypeScript,缺口是接口契约。Python 接受任何带对应方法的对象,TypeScript 要求先定义 Handler 接口,把对象的方法、参数和返回值明确写下来。

Mike 的项目在第六阶段还多走了一步:把七个真实场景逐一交给修复 agent,直到新旧输出全部一致;随后,Claude 又自行设计了一套端到端测试,连续四个晚上自主执行、发现问题并修复,捕获了场景清单没有预先覆盖的边缘问题。

§VI
同一台机器,两种路线

保留结构,还是重新设计

Bun 和 Mike 的项目都使用了六阶段循环,但开局选择不同,因此流程细节差异很大。

Bun 选择保留原有结构,Mike 选择重新设计。这一个选择,会一路影响到规则手册的形态、缺口清单的时机、编译器放在哪里,以及整条流水线要跑几遍。

对比维度
保留结构Bun · Zig → Rust
重新设计内部工具 · Python → TypeScript
规则手册形态
一份类型与惯用写法的映射表
一份完整设计文档(结构本就要重来)
缺口清单时机
正式翻译前先建好
首轮实现后回头审计补齐
编译器位置
移出高频循环,推迟到第四阶段(cargo 编译一次要几分钟)
放进每次循环(tsc 检查单个单元只要几秒,错误尽早暴露)
压测方式
同一文件两种译法逐行对照
攻击设计文档 + 一次完整迁移验证
整条线跑几遍
加上评审和门禁,基本一次完成
完整跑三遍,前两遍代码全丢,只留第三遍

Mike 前两遍的价值在于改进设计、规则和编排,而不是交付代码。这说明流程没有固定模板:即使使用相同的六个阶段,也要根据目标结构、编译成本和测试条件重新安排。

§VII
官方总结的几条经验

评审要有敌意,判定要靠机器

01
关注模式,而非单次失败
单个 agent 失败是修复循环要处理的日常。真正值得人介入的,是重复出现的模式,它通常说明规则、依赖图或设计文档存在缺陷。
02
评审带敌意,判定靠工具
评审 agent 不能只收到「检查这段代码」。更有效的方式,是要求它先假设代码有错,再从源码、测试和文档里逐条找证据。最终是否通过,交给编译器、测试和 diff。
03
不要用最大模型做所有事
高吞吐的代码生成交给成本较低的小模型;大模型集中处理评审和规则,因为一条规则会被大量 agent 重复执行,其正确性比单个文件更重要。
04
把人的时间前置
规则手册、依赖分析和小规模试航最需要工程判断。进入全量生成后,主要工作应当由队列、模型和机器裁判完成。
§VIII
迁移之后

收益不只是换了一种语言

Bun 的 Rust 版本已经运行在生产环境中。它并非完全没有代价:约 4% 的 Rust 代码位于 unsafe 块中,多数是与 C、C++ 边界交互时的单行指针操作。但迁移结果可以被量化。

6745 → 609 MB
连续执行 2000 次构建的基准里,内存占用大幅下降,工具能检测到的内存泄漏已全部修复。
缩小 19%
Linux 与 Windows 的二进制体积;真实负载还获得 2%–5% 的性能提升。
八分钟 → 两秒
Mike 项目单平台的编译时间;二进制启动快六倍,并取消了一条独立部署流水线。

更重要的变化仍然是风险。过去最危险的情况,是投入数年后发现新系统只有九成行为与旧系统一致,团队既无法继续迁移,也无法轻易退回。现在,失败的主要成本变成 token、API 费用和几天运行时间;流程有问题,就删除结果、修正规则、再跑一遍。

§IX
收尾

真正值得复用的,是那种思路

这并不意味着所有迁移都值得做,也不意味着可以照搬某套固定脚本。每个代码库的裁判、缺口、依赖和目标结构都不同,正式开始前仍然需要先为具体项目设计流程。Anthropic 提供了一个迁移入门工具包,但它只是对通用流程的抽象,并不是 Bun 或 Mike 项目实际使用的原始系统;另一个 code-modernization 插件主要服务于遗留系统现代化和框架升级,也不等同于跨语言重写。

真正值得复用的不是某份模板,而是一种工程思路:不要把大规模迁移看成无数次代码修改。先建立能够持续生成、持续质疑、持续判分的循环,然后把人的判断写进规则,把机器的确定性写进门禁,让流水线自己把规模消化掉。