Anthropic 如何借助 Claude Code 进行大规模代码迁移(11 分钟阅读)
摘要
Anthropic 概述了使用 Claude Code 进行大规模代码迁移的六步流程,重点包括创建规则手册、依赖关系映射以及通过强大的评审模型进行迭代验证。
Anthropic 使用 Claude Code 遵循六步流程进行大规模代码迁移,重点包括创建规则手册、分析依赖关系以及对翻译规则进行压力测试。它部署多个智能体以迭代方式翻译、审查和修复代码,并强调使用对抗性审查和机械验证来实现高效迁移。
查看缓存全文
缓存时间: 2026/07/20 09:44
# Anthropic 如何使用 Claude Code 进行大规模代码迁移
来源:https://claude.com/blog/ai-code-migration
## **大规模代码迁移的六个步骤**
*以下流程已进行泛化,适用于多种语言和场景。更多详情可阅读**Jarred 的博客**(https://bun.com/blog/bun-in-rust)。*
### **先决条件**
开始迁移项目之前,需要有一个可靠的评判机制,否则无法定义退出条件或衡量成功。这个评判机制必须能够平等地评估原始代码和目标代码。用原始语言编写的测试套件通常会依赖目标代码中不存在的内部函数。要构建这样的评判机制:
- **归类现有测试**。使用 Claude 识别哪些测试可以表示为外部调用,哪些依赖无法移植的内部函数。
- **重写以提高可移植性**。将面向外部的测试转换为可以对原始代码和移植代码同时运行的断言。使用对抗性代理验证重写后的测试没有削弱断言。
- **验证评判机制**。在原始代码上运行,确认通过。然后在故意损坏的代码上运行,确认失败——不能捕获错误的评判机制不是评判机制。
Jarred 有一个用第三种语言(TypeScript)编写的大型测试套件,但大多数项目并非如此。对于 Python 到 TypeScript 的移植,Mike 创建了一个包含七个真实场景的一致性检查工具,并将任何行为变化视为需要修复的 bug。
在我们进入每个阶段之前,下面的图表可能有助于理解。这主要遵循 Jarred 的方法,每个阶段都有审查和门控。Mike 使用了类似的整体结构和相似的工作循环,但他从头到尾完整运行了一次迁移,根据结果修改规则和工作流,然后再次运行——每次丢弃输出,直到第三次运行。
### **步骤 1 — 创建规则手册、依赖关系图和缺口清单**
在这一阶段,我们创建迁移的基础:一份需要重构(而非仅仅翻译)的代码位置的清单,一份代码翻译的规则手册,以及一份依赖关系图,以排序迁移实现工作流。顺序很重要:规则手册必须在缺口清单之前。缺口清单由规则手册默认情况下无法覆盖的内容定义,两者在一次联合审计中一起测试。
#### **规则手册**
规则手册的具体形式取决于一开始必须做出的关键架构决策。首先是新代码将遵循相同的结构,还是完全重新设计。如果是前者(Jarred),规则手册主要是查找表,用于翻译语言间的类型和惯用法,同时指向缺口清单中更难翻译的组件。如果是后者(Mike),它将是一份设计文档。
Jarred 通过与 Claude 对话创建了他的规则手册,为每个模糊区域制定策略。他还使用了八个子代理,专门基于自己的直觉审查八类常见失败模式。
#### **依赖关系图**
你需要理解文件依赖关系,以便有效地分解工作流进行并行迁移,知道哪些文件应该先迁移,哪些文件应该放在同一批次中。有些语言和代码库有显式的清单文件,使这一点很容易,但对于遗留代码库和许多流行语言(如 C/C++ 和 Python),这些依赖关系需要被发现和映射。
Claude Code 可以部署代理来创建并运行一个确定性脚本以生成此映射。迁移工具包中的提示 (https://github.com/anthropics/code-migration-kit-with-claude-code/blob/main/prompts/01-dependency-map.md) 使用工作流创建了一个审查-修复循环。
*注意:入门工具包是本文所述流程的通用模板——并不是这些特定移植所运行的工具。*
#### **缺口清单和怀疑论审查者**
新语言与旧语言有不同的要求,必须满足。对于 Zig 到 Rust,差异在于手动内存管理(C 和 C++ 也是如此)。例如:
Zig
``
fn readConfig(allocator: std.mem.Allocator) ![]u8 {
const buf = try allocator.alloc(u8, 1024);
// ...fill buf...
return buf; // 调用者必须释放它——但只有注释这么说
}
// 忘记 'defer allocator.free(buf)' 的调用者仍然能编译通过——只有运行时才会出现泄漏。
``
Rust
``
fn read_config() -> Vec<u8> {
let buf = vec![0u8; 1024];
// ...fill buf...
buf // 所有权移交给调用者;内存自动释放
}
// 移用后使用?双重释放?两者都无法编译通过。
// 忘记释放?根本就没有释放调用——丢弃是自动的。
``
对于 Python 到 TypeScript,缺口在于接口和契约。Python 不需要声明接受什么形状的对象或返回什么,但 TypeScript 需要。例如:
Python
``
def register(handler):
handler.setup()
return handler.run({"retries": 3})
# 任何具有 .setup() 和 .run() 的对象都可以在这里工作。实际传入的是哪些对象?需要阅读整个代码库才能知道。
``
TypeScript
``
interface RunResult { ok: boolean }
interface Handler {
setup(): void;
run(opts: { retries: number }): Promise<RunResult>;
}
function register(handler: Handler): Promise<RunResult> {
handler.setup();
return handler.run({ retries: 3 });
}
// 在编译之前必须写下这个契约
``
Jarred 和 Mike 都创建了捕获这种隐式知识的缺口清单文件。Jarred 提前清点了这些缺口,正如我们在这里所做的;而 Mike 选择先翻译,然后通过事后审计创建缺口清单。你可能两者都需要。查看这个示例 Claude Code 提示来创建缺口清单文件 (https://github.com/anthropics/code-migration-kit-with-claude-code/blob/main/prompts/02-gap-inventory.md)。
### **步骤 2 — 对规则进行压力测试**
这一步涉及一次小型迁移,作为大规模迁移的“试航”。在这一步中,Jarred 使用一个代理根据规则手册翻译三个文件,一个代理“像资深 Rust 工程师一样”翻译三个文件,再一个代理使用差异来创建新的翻译规则。在这个阶段,他发现了两个关键问题,如果散布到全部 1,448 个文件中,会造成大量问题。提示可能类似于这个 (https://github.com/anthropics/code-migration-kit-with-claude-code/blob/main/prompts/03-stress-test.md)。
这种类型的压力测试**仅适用于保留结构的迁移**,即同一文件的两个翻译可以逐行比较。如果你的规则手册是重新设计——像 Mike 的那样——等效的测试是直接使用对抗性审查者攻击设计文档,然后通过一次性的端到端运行来验证。无论如何,扔掉所有翻译的文件。目标是完善规则,而不是取得渐进进展。
### **步骤 3 — 翻译所有内容**
在接下来的步骤中,你会运行相同的多代理循环架构:实现、审查、修复。你可以将实现者工作卸载给较小模型,将审查者保留在较大模型上。例如,Mike 在主要迁移中派出 12 个子代理时使用了 Claude Sonnet。工作队列应该是机械式的。一个批处理脚本通过检查翻译文件是否存在于磁盘上来决定哪些已完成,然后将待处理文件切割成批次给实现者代理。由于队列每次从磁盘重建,迁移本质上是可恢复的。
在这个阶段,代理可能会过于谨慎,做过多工作。修复方法可以是一条直接而有力的提示指令,并附带上下文说明编译器会在下一步捕获错误。任何翻译者不能自信执行的内容都会被标记为 `// TODO(port):` 以便在步骤 4 中处理。
从此以后,待办列表会自动生成:编译器枚举错误,冒烟测试发现崩溃,测试套件报告失败。两个对抗性审查者使用不同上下文评估实现者的工作,审查者之间的分歧交给第三个代理。当审查者在多个文件中反复发现相同错误时,修复不是针对每个文件。你向规则手册添加一句话,然后重新生成受影响的批次。规则手册在这一步中持续增长;代码永远不会被手动修补。
这一步中需要注意的一个重要设计决策是编译器所在位置。Mike 在每个循环内运行 TypeScript 编译器,因为它在几秒内检查一个单元。Jarred 则禁止编译器进入循环,将其推迟到下一步,因为 cargo 需要几分钟。
在这一步,大部分繁重工作已完成,提示开始变短 (https://github.com/anthropics/code-migration-kit-with-claude-code/blob/main/prompts/04-translation-kickoff.md)。
### **步骤 4、5、6 — 编译、运行、匹配行为**
这三个步骤共享相同的循环架构,所需的判断逐渐减少,因此我们将它们一起讨论。
**步骤 4** 例如,根据语言和迁移的规模,常常会融入步骤 3。根据编译器步骤的大小和难度,代理可能根本不运行这个循环。Jarred 使用一个编排脚本在整个工作空间上调用编译器一次。“修复代理”随后并行处理错误列表并进行对抗性审查。构建再次运行,重复。审查错误列表有助于发现可能需要调整的系统性问题。例如,Jarred 遇到了数千个 Rust 模块错误,这些错误是在修复了 Zig 的惰性编译容忍的循环导入后才出现的。他通过编码逻辑来分类删除、移动或重新组织边界依赖来修复循环。
**步骤 5** 也有一个类似编译器错误列表的机械真相源:来自冒烟测试的崩溃。同样,循环修复是将问题分组到类别中,本例中按根本原因分组,由对抗性子代理审查。
**步骤 6** 也是我们故事的结尾:比较两个代码库中程序的行为。现在我们的文件已翻译、编译并通过了冒烟测试。现在是时候将它们分片并在其上运行测试套件(来自先决条件阶段)。使用“修复代理”处理失败情况,这些代理针对两个代码库审查失败的测试。对抗性审查者检查他们的修复。这个循环的下一阶段是一个构建守护进程 (https://github.com/anthropics/code-migration-kit-with-claude-code/blob/main/scripts/build_daemon.sh),它是唯一允许重新构建二进制文件的进程。修复者编写补丁;守护进程将它们分批,一次重建,重新运行受影响的测试,并将结果反馈回来。这序列化了最昂贵的操作,而不是让多个代理独立触发它。当相同失败在多个测试中重复时,修复上移:你修改产生错误的规则,并仅重新生成受该规则影响的文件。
Mike 的方法在这里很重要,因为许多开发者不会有现成的或移植的测试套件。Mike 让 Claude 创建一个小脚本,在新移植版本和原始 Python 代码库上运行 7 个真实场景,并对结果进行 diff。每个失败的场景都有自己的修复代理,循环运行直到全部七个通过。然后他更进一步。Claude 设计了自己的端到端测试套件,并自主运行了一整夜,修复了出现的问题,连续运行了四个晚上。结果,它捕获了任何场景列表都无法预见的小问题。
教训是,缺少测试套件并不阻碍这一步。如果你不能继承一个裁判,就让 Claude 构建一个。无论如何,你的原始代码库是事实依据。
## **代码迁移最佳实践**
每次运行都教会了我们之前没有学到的东西。可以确信,你的下一次迁移也会教会你本指南无法预见的经验。但有几个实践在每一个项目中都成立:
- **不要盲目遵循本指南。** 每次迁移都不同。将其视为起点,并在开始之前使用 Claude 规划你的特定迁移。
- **不要关注个别失败。** 个别失败是循环的工作。修复代理会逐步解决它们。你的注意力应该放在模式上。
- **使审查具有对抗性,验证具有机械性。** 对抗性审查允许较长时间运行的任务,通常值得消耗 tokens。让脚本——编译器、diff、测试套件——充当裁判。
- **不要对所有事都使用最大模型。** Token 消耗集中在循环中,因此要仔细设计它们。较小的模型能很好地处理高容量的实现派发;将最大的模型留给审查者和编写其他代理将遵循的规则。
- **提前投入人力时间。** 规则手册和压力测试是最耗时的。之后的一切主要是队列的消耗。
- **使工作队列机械且可恢复。** “完成”应该意味着“输出文件存在于磁盘上”。
## **审查循环结果,而非代码**
Jarred 的 Bun 迁移已投入生产,尽管每次迁移都有权衡。例如,大约 4% 的 Rust 代码位于“不安全”块中,主要是 C/C++ 边界的单行指针操作。但新的代码库可测量地更好。团队工具能检测到的每个内存泄漏都已修复:一个 2,000 次重复构建的基准测试从 6,745 MB 内存降至 609 MB。二进制文件在 Linux 和 Windows 上缩小了 19%。跨语言优化使其在 HTTP 服务和真实工作负载(如 next build 和 tsc)上快 2-5%。
考虑一下是否应该重新评估你长期推迟的迁移的收益。选择你一直容忍的代码库,向 Claude 询问它的迁移过程是什么样的。
***
*相关*
- *迁移入门工具包* (https://github.com/anthropics/code-migration-kit-with-claude-code)
*注意:入门工具包是上述流程的通用模板——并不是这些特定移植所运行的。*
- *代码现代化插件* (https://github.com/anthropics/claude-plugins-official/tree/main/plugins/code-modernization)——用于遗留现代化和框架升级,而非语言移植
- *Claude Code 中的动态工作流* (https://claude.com/blog/introducing-dynamic-workflows-in-claude-code)
相似文章
Claude Code 在大型代码库中的工作原理
Anthropic 的博文详细介绍了在大型复杂代码库中使用 Claude Code 的最佳实践,阐述了代理搜索以及如 CLAUDE.md 文件等扩展的“利用”如何在大规模下提升导航和性能。
Anthropic表示,其80%的新生产代码现由Claude编写——企业如何跟上步伐(7分钟阅读)
Anthropic报告称,超过80%的新生产代码由Claude编写,每位工程师交付的代码量提升了8倍。本文为企业采用类似AI驱动开发工作流程提供了路线图。
一篇详细描述Anthropic自身工程师使用Claude Code时的确切配置和工作流程的文章,包括并行实例、CLAUDE.md模式、写作者/审阅者分离、技能文件夹、插件、钩子和批量操作。
一篇详细介绍Anthropic工程师在使用Claude Code时的具体配置和工作流程的文章,涵盖并行实例、CLAUDE.md模式、写作者/审阅者分离、技能文件夹、插件、钩子和批量操作。
@PrajwalTomar_: 你不明白这有多重大。Anthropic 刚发布了让 Claude Code 无需你干预的四种方法...
Anthropic 发布了四种让 Claude Code 自主运行的循环类型:基于回合、基于目标、基于时间和主动式,允许不同级别的任务交接。
@unicodef1wn: https://x.com/unicodef1wn/status/2070179071548395916
一篇推文解释了Anthropic在Claude Code中的动态工作流如何让Claude为复杂任务构建自定义框架,通过将工作拆分到不同的智能体来防止智能体惰性、自我偏好偏差和目标漂移等失败模式。内容包含供用户参考的实用示例和模式。