两周搬完一百万行代码:Anthropic用Claude把多年不敢碰的语言迁移压进一个周末

核心提示把一门编程语言整套换掉,这种事放在过去,基本等于给公司立一个两年起步的军令状。工程师们谈之色变,预算委员会闻风丧胆,代码库却年复一年卡在原地动弹不得。Anthropic在7月16日甩出一篇文章,直接把这笔旧账翻了篇:过去一个月里,他们内部的

两周搬完一百万行代码:Anthropic用Claude把多年不敢碰的语言迁移压进一个周末

把一门编程语言整套换掉,这种事放在过去,基本等于给公司立一个两年起步的军令状。工程师们谈之色变,预算委员会闻风丧胆,代码库却年复一年卡在原地动弹不得。Anthropic在7月16日甩出一篇文章,直接把这笔旧账翻了篇:过去一个月里,他们内部的开发者用Claude Fable5、Opus4.8和动态工作流,把10个代码包、每个数万到数十万行的规模,干净利落地搬到了新语言上。这不再是将来时态的设想,而是已经发生在他们自己身上的事实。

最刺眼的两个案例像两颗钉子,把想象钉成了现实。Bun的联合创始人Jarred Sumner,用Claude Code把整个Bun从Zig迁移到了Rust,不到两周产出一百万行代码,合并进主干前整条CI测试链100%通过,合并后浮出19个回归,如今已全部修掉,Rust版本6月就已经随Claude Code发布。另一边,Anthropic Labs的联合负责人Mike Krieger,一个周末就把一套Python代码库搬成了16.5万行Typescript,里面塞了数百个代理、8道阶段闸门、3轮对抗审查,最后还用一套奇偶校验,把每条命令的输出和Python原版逐字比对。

Anthropic把这整套打法提炼成一句话,也是全文最锋利的那把刀:你修的不是代码,而是生产代码的那个流程(循环)本身。一旦想通这一点,那些被无限期搁置的迁移项目,就不再是不可触碰的禁区。

先说清楚什么时候该动、为什么动,因为这件事的前提早就变了。团队启动迁移,通常是因为项目诞生时的技术地形,和今天已经对不上号:要么当初那个心知肚明的权衡终于卡住了脖子,要么出现了更好的路,要么原来的生态正在萎缩。Jarred当初选Zig,图的是C级别的性能加上极致的简单,特别适合一个创始人在奥克兰狭小的公寓里、大模型还没出现的年代,用一年时间把Bun写出来。那份简单是有代价的,他后来专门写过文章坦白。

快进到2026年,Bun的命令行工具月下载量已经突破一千万,在Claude Code内部也被大规模使用。就在上季度,这些代价还不足以让人冻结路线图、押上几个季度的资源去做一件大事。迁移语言确实能换来更小、更快、更安全的系统,可没人有动力去付这笔账。工程师们还得掂量其中的职业风险:你可能并行维护两套代码好几年,最后只换来90%的等价,那比一开始就别动还头疼。

现在不一样了。最坏的结果无非是删掉那条分支,重头再来。当然,商业账还是要算的。一百万行级别的迁移,虽然不必再像四年项目那样烧掉三四百万美元的工程师资源,但落地依旧要花数万到数十万美元往上。Bun那次迁移吞掉了59亿个未缓存的输入token和6.9亿个输出token,按API定价约合16.5万美元;Mike主迁移那块也烧了2700万token。真正让Mike下决心动手的,是编译这道坎。他团队的内部工具以单个二进制文件交付用户,用Python工具链为每个平台编译要花大约八分钟,整个构建矩阵一轮下来得等三十分钟。搬完之后,同样的编译只要两秒,二进制启动速度快了6倍,还顺手淘汰了一条独立的部署管线。

为什么AI能把这道旧账算平?因为大规模代码迁移恰好是这些高级模型的天选场景。活儿是天然并行的,文件、crate这种成百上千的独立单元,可以让代理同时开干,谁也不用等谁。上下文既清晰又完整,旧代码本身就是给模型最好的规格说明书,也是翻译代理照着走的指南蓝本。更妙的是自带裁判,大型代码库里往往躺着一套测试套件,代理能用它来给自己打分,当验证足够客观,模型可以盯着真相死磕好几天,不需要人坐在旁边判质量。队列还会自己写:编译器或测试一挂,下一个要修的活儿就自动排上了。流程本身让偏差无处藏身,审查员每一条发现都要引用背后的规则,于是一次违规变成一个待办项,而不是悄悄分叉;某个代理撞上边缘情况,修法就成了之后所有代理都要遵守的规则。下面两个案例里,Mike和Jarred都在关键步骤用上了Fable,尤其是一种顾问模式,让多个不同档位的模型分工,把token消耗压到最低。

真正能复用的,是他们总结出的六步法,从这两场迁移里熬出来,又做了泛化,能套到多种语言和场景上。

开工前得先备好一个铁面无私的裁判,否则你既不知道何时收工,也没法衡量成败。这个裁判必须能站在同一标准上同时评判原代码和目标代码。用原语言写的测试套件,往往依赖目标代码里根本不存在的内部函数。要把它造出来,先用Claude把现有测试分个类,哪些是能用外部调用表达的,哪些依赖搬不过去的内部实现;再把面向外部的测试改写成能同时跑在原始代码和移植版上的断言,并派对抗代理去验证改写没有削弱断言力度;最后拿原始代码跑一遍确认它能过,再拿故意改坏的代码跑一遍确认它会挂,一个抓不出破损的裁判,算不上裁判。Mike那次Python转Typescript,就造了一个覆盖7个真实场景的奇偶校验台,任何行为变动都算要修的bug。

第一步是立规矩、画依赖图、列缺口清单,给整场迁移打下地基。顺序有讲究:规则手册必须排在缺口清单前面,因为缺口正是由规则手册的默认项覆盖不到的地方定义的,两者还要在联合审计里一起接受检验。规则手册长什么样,取决于你一开始做的核心架构决定:新代码是沿用老结构,还是彻底重做。沿用结构(Jarred那路),手册主要就是一张张把类型和惯用法在两种语言间翻译的查表,遇到难翻的组件再指向缺口清单;彻底重做(Mike那路),手册就是一份设计文档。Jarred是跟Claude聊出来的,给每个含糊地带立一条政策,还专门派了8个子代理,各盯一类他自己凭直觉列出的常见失败模式。依赖图要摸清文件间的依赖,才能把活儿有效地切成并行流,知道哪些先搬、哪些要打包在一起。有些语言和代码库自带清单文件,这事不难,但面对C/C++、Python这类老代码,依赖得自己探出来、画出来,Claude Code能派出代理跑一个确定性脚本把图生成。缺口清单记的是新旧语言之间的硬差距:Zig转Rust,差在手动内存管理,一个忘了释放的缓冲区,编译能过,漏不漏只有运行时才知道;Python转Typescript,差在接口和契约,Python不要求声明对象长什么样,Typescript却要白纸黑字写下来才肯编译。Jarred和Mike都把这些隐性知识记进了缺口清单文件,Jarred是前期就盘清楚,Mike是先翻再补,你可能两头都得做。

第二步压一压规则,来一场迷你迁移当试航。Jarred派了一个代理照手册翻三个文件,一个代理像资深Rust工程师那样翻三个,再一个代理拿着两份差异去提炼新规则。就在这个阶段,他逮住了两个致命问题,要是等摊到全部1448个文件上再爆,麻烦就大了。这套压力测试只对结构保持型迁移有效,因为同一份文件的两版翻译能逐行比。要是你的手册是重设计,像Mike那样,等价做法是直接拿对抗审查员去炮轰设计文档,再用一次可丢弃的端到端跑通来验证。无论如何,翻出来的文件全部扔掉,这一步的目的只是把规则磨利,不是攒进度。

第三步翻译一切,剩下的步骤都跑同一套多代理循环:实现、审查、修复。实现这种苦力活能甩给小模型,审查留给大块头,Mike铺开12个子代理时用的就是Claude Sonnet。任务队列要机械到无聊:一个批处理脚本看翻译后的文件在不在磁盘上,来决定哪些算完工,再把待办切片丢给实现代理;因为队列每次都从磁盘重建,迁移天生就能断点续跑。代理有时候会过于谨慎、干得太少,解法就是一条干脆利落、带着上下文的指令,提醒它下一步编译器会替它抓错。翻译器没把握的活儿,标上一行// TODO(port): 原因,留给第四步。从这儿往后,待办清单自己会写:编译器数出错,冒烟测试逮住崩溃,测试套件报出失败。两个对抗审查员用各自的上下文给实现者的活儿打分,两者意见相左就交给第三个代理。当一个审查员在多个文件里反复抓到同一种错,修法不是逐文件打补丁,而是在规则手册里加一句话,把受影响的批次重新生成。这一步里手册不断变厚,代码却从不被手工对着它改。还有一个关键设计决定是编译器放在哪:Mike把Typescript编译器塞进了每一个循环,因为它几秒就能查一个单元;Jarred则把编译器彻底挡在循环外、推迟到下一步,因为cargo要跑好几分钟。

第四、五、六步合在一起说,因为它们共用同一套循环,且越往后越不需要人的判断。第四步编译,取决于语言和规模,它常常融进第三步。Jarred用一个编排脚本在整个工作区调一次编译器,修复代理再并行扫着错误清单干,对抗审查同步进行,构建重跑,如此往复。翻错误清单能帮你抓出系统性的毛病,比如Jarred修完Zig宽松编译所容忍的循环依赖后,冒出几千个Rust模块错误,他给循环加了一段逻辑来分类该删、该挪、还是该重构边界,才算压住。第五步和编译器错误清单一样有机械真相来源:冒烟测试吐出的崩溃,修法依旧是按根因归类、交给对抗子代理审。第六步,也就是故事的尾声,是拿两套代码库的行为逐项比对。文件翻译完、编译完、冒烟完,就把它切碎,把预备阶段那份测试套件套上去跑,失败的交给修复代理对着两套代码审,对抗审查员再查它们的修法。循环再往后是一道构建守护进程,它是唯一被允许重建二进制的角色,修复代理只写补丁,守护进程把它们攒批、重建一次、重跑受影响的测试、再把结果喂回来,把最贵的操作串行化,免得一群代理各自触发。当同一个失败在大量测试里反复出现,修法要往上游走:改掉生出这个bug的规则,只重新生成它碰过的文件。Mike这招尤其值得记:很多开发者手头没有现成或移植好的测试套件,他让Claude写了个小脚本,拿7个真实场景同时跑新移植版和Python原版,比输出差异,每个挂掉的场景配一个修复代理,循环跑到7个全过。他还更进一步,让Claude自己设计了端到端测试套件,通宵自主运行,哪里崩就修哪里,连跑四个晚上,逮住了任何场景清单都预测不到的那些细小的伤。

这套打法跑下来,有几条实践在每个项目上都站得住,但Anthropic也提醒,别盲信这份指南,每场迁移都不一样,把它当起点,先跟Claude把你那场的具体方案聊清楚再动手。别盯着单点失败,那是循环的活儿,修复代理会替你把它们磨平,你的注意力该放在规律上。让审查保持对抗、让验证保持机械,对抗审查能撑起更长的任务,多花的那点token往往值;让编译器、差异比对、测试套件这些脚本去当裁判。别什么事都上最大模型,token开销集中在你的循环里,要刻意设计,小模型扛得住高吞吐的实现铺开,把最大的模型留给审查员,以及任何写规则手册、要被其他代理照着走的人。把人的工时前置,规则手册和压力测试最耗时间,之后基本就是队列在烧。让任务队列机械且可续跑,完工的定义就是输出文件已经躺在磁盘上。

回到结果本身,而不是代码细节。Jarred的Bun迁移已经上线生产,当然任何迁移都有取舍,大约4%的Rust代码待在unsafe块里,主要是C/C++边界上那些单行指针操作。但新代码库是肉眼可见地更好:团队工具能侦测到的内存泄漏全修干净了,一个重复构建2000次的基准测试,内存占用从6745兆字节掉到609兆;二进制在Linux和Windows上小了19%;跨语言优化让它在HTTP服务和next build、tsc这类真实负载上快了2%到5%。

那些你一直忍着没动、将就着用的代码库,也许是时候重新算算这笔账了。挑出那个你容忍最久的家伙,问问Claude,为它做一场迁移到底长什么样。

今日推荐