大规模代码迁移的六个步骤

以下流程经过泛化,适用于多种语言和场景。更多细节可参考 Jarred 的博客

前提条件

在开始迁移项目之前,必须建立一个强有力的判定机制,否则无法确定迁移的结束条件或成功标准。

判定机制需要能够公平地评估原始代码和目标代码。原语言编写的测试套件通常依赖于目标代码中不存在的内部函数。

构建判定机制的步骤包括:

  • 分类现有测试:利用 Claude 识别哪些测试可以通过外部调用表达,哪些依赖于无法迁移的内部实现。
  • 重写以实现可移植性:将面向外部的测试转换为可同时针对原始代码和迁移代码运行的断言。使用对抗性代理验证重写后的测试断言未被削弱。
  • 验证判定机制:先在原始代码上运行确认通过,再在故意破坏的代码上运行确认失败。无法捕捉错误的判定机制不合格。

Jarred 拥有用第三语言(TypeScript)编写的大型测试套件,但大多数项目并非如此。Mike 在 Python 到 TypeScript 的迁移中,创建了包含七个真实场景的对比测试,任何行为差异都视为需修复的缺陷。

以下图示帮助理解整体流程,主要基于 Jarred 的方法论,每个阶段均设有评审和关卡。Mike 采用类似结构和循环工作流,完成全流程迁移后根据结果调整规则和流程,反复运行三次,最终保留第三次结果。

迁移流程图

步骤1 — 制定规则手册、依赖关系图和差距清单

步骤1

此阶段构建迁移基础:列出需要重构而非简单翻译的代码点,制定代码翻译规则手册,绘制依赖关系图以安排迁移工作流。

顺序很重要:规则手册必须先于差距清单。差距清单定义了规则手册默认规则无法覆盖的部分,两者通过联合审核进行验证。

规则手册

规则手册的具体形式取决于项目初期的关键架构决策,尤其是新代码是否保持原有结构或进行完全重设计。

  • 若保持结构(如 Jarred),规则手册主要是类型和语言习惯的查找表,复杂部分指向差距清单。
  • 若重设计(如 Mike),则是设计文档。

Jarred 通过与 Claude 对话,针对每个模糊点制定策略,并使用八个子代理分别审查八类常见失败模式。

依赖关系图

理解文件间依赖关系对于并行迁移至关重要,决定先迁移哪些文件,哪些文件应归为同一批次。部分语言和代码库有显式清单,便于绘制依赖图;但许多遗留代码库及 C/C++、Python 等语言需通过分析发现依赖。

Claude Code 可部署代理运行确定性脚本生成依赖图。迁移套件中的依赖图提示采用审核修正循环。

注意:迁移套件为本文流程的通用模板,非本文案例实际使用。

差距清单与质疑审查者

新语言对旧语言有不同要求,必须明确差距。例如,Zig 到 Rust 的差距是手动内存管理与自动内存管理的区别:

Zig 示例:

fn readConfig(allocator: std.mem.Allocator) ![]u8 {
    const buf = try allocator.alloc(u8, 1024);
    // ...fill buf...
    return buf; // 调用者需释放,但仅注释说明
}

// 忘记释放仍能编译,内存泄漏仅在运行时显现。

Rust 示例:

fn read_config() -> Vec {
    let buf = vec![0u8; 1024];
    // ...fill buf...
    buf // 所有权转移,自动释放
}
// 使用已转移的变量或重复释放均无法编译。

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;
}

function register(handler: Handler): Promise {
    handler.setup();
    return handler.run({ retries: 3 });
}
// 编译前必须声明接口契约。

Jarred 和 Mike 都创建了差距清单文件,捕获隐含知识。Jarred 先列出差距,Mike 先翻译后审计生成差距清单,实际项目可能需结合两者。

示例差距清单提示见此处

步骤2 — 规则压力测试

步骤2

此阶段进行小规模迁移,作为大规模迁移的“试航”。

Jarred 使用三个代理分别:基于规则手册翻译三份文件;以“高级 Rust 工程师”身份翻译三份文件;根据差异生成新规则。此阶段发现两处关键问题,若扩散至全部1448个文件将引发大量问题。

压力测试提示示例见此处

此类压力测试仅适用于结构保持迁移(文件逐行可比对)。若规则手册为重设计(如 Mike),则需对设计文档进行对抗性审查,并用一次性端到端运行验证。

无论如何,测试完成后丢弃翻译文件,目标是完善规则而非推进进度。

步骤3 — 全面翻译

步骤3

后续步骤采用多代理循环架构:实现、审查、修正。

可将实现任务分配给较小模型,审查任务由大模型完成。例如,Mike 使用 Claude Sonnet 托管12个子代理执行主迁移。

工作队列应机械化管理。批处理脚本通过检查磁盘上是否存在翻译文件决定任务完成情况,并将待处理文件分批分配给实现代理。因队列每次均从磁盘重建,迁移过程天然支持断点续传。

此阶段代理可能过于谨慎,工作量有限。可通过明确指令告知代理,编译器将在下一阶段捕捉错误。

无法自信执行的翻译处标记为 // TODO(port): ,留待步骤4处理。此后,错误列表由编译器、冒烟测试和测试套件自动生成。

两名对抗性审查者分别评估实现者工作,若意见不合交由第三代理裁定。若审查者在多文件中反复发现同一错误,修正不再逐文件进行,而是向规则手册添加一句话并重新生成受影响批次。规则手册在此阶段持续增长,代码不做手工修补。

此阶段重要设计决策之一是编译器的使用位置。Mike 在每个循环中运行 TypeScript 编译器(编译速度快),Jarred 则完全排除编译器,留待下一阶段(cargo 编译耗时较长)。

此时,大部分繁重工作已完成,提示词开始简化

步骤4、5、6 — 编译、运行与行为匹配

步骤4-6

这三个步骤采用相同循环架构,且对人工判断依赖逐步减少,故合并介绍。

  • 步骤4 通常与步骤3融合,具体视语言和迁移规模而定。部分项目中,代理甚至不执行此步骤。Jarred 通过协调脚本调用编译器一次,随后“修复代理”并行处理错误列表,配合对抗性审查,反复构建。

  • 审查错误列表有助于发现系统性问题。例如,Jarred 解决了 Rust 模块错误,源于 Zig 的惰性编译容忍循环导入,修正逻辑后循环稳定。

  • 步骤5 依赖冒烟测试崩溃报告作为机械真相源,修复循环通过对错误原因分类,由对抗性子代理审查。

  • 步骤6 是最终对比两个代码库程序行为。文件已翻译、编译并通过冒烟测试,现分片运行测试套件(前期准备阶段构建)。失败测试由“修复代理”对比两端代码审查,对抗性审查确认修复。

下一阶段是构建守护进程,唯一允许重建二进制文件的进程。修复代理提交补丁,守护进程批量构建、重测并反馈结果,避免多代理重复触发构建。

重复失败跨多个测试时,修复上移至规则手册,修改规则后仅重新生成受影响文件。

Mike 的方法尤为重要,因为许多开发者缺乏完善测试套件。Mike 让 Claude 编写脚本,针对七个真实场景同时运行新旧代码并对比结果,失败场景由专门修复代理处理,循环至全部通过。

更进一步,Claude 自行设计端到端测试套件,连续四晚自动运行、修复并重测,捕获了场景列表未预见的细节问题。

经验表明,缺少测试套件并不阻碍此步骤。若无法继承判定者,可让 Claude 构建一个。无论如何,原始代码库是最终真相。

代码迁移最佳实践

每次迁移都会带来新的经验,以下做法在多个项目中均有效:

  • 不要盲目遵循本指南。每次迁移情况不同,应将本指南作为起点,先用 Claude 规划具体迁移方案。
  • 不必纠结单个失败。失败是循环的工作内容,修复代理负责解决。关注失败模式更重要。
  • 让审查具备对抗性,验证机械化。对抗性审查适合长时间任务,值得消耗更多资源。让编译器、差异工具和测试套件充当裁判。
  • 不要用最大模型处理所有任务。令牌消耗集中在循环中,合理设计。小模型适合大量实现任务,大模型留给审查和规则制定。
  • 前置人力投入。规则手册和压力测试最耗时,后续多为队列消化。
  • 使工作队列机械化且可断点续传。完成应以“输出文件存在磁盘”为准。

关注审查循环结果,而非代码

Jarred 的 Bun 迁移已投入生产,尽管每次迁移都有权衡。例如,约4%的 Rust 代码包含 "unsafe" 块,主要是单行指针操作,位于 C/C++ 边界。

但新代码库明显优于旧版:检测到的内存泄漏全部修复,2000次重复构建的内存峰值从6745MB降至609MB,Linux 和 Windows 下二进制文件体积缩小19%,跨语言优化使 HTTP 服务及实际工作负载(如 next build 和 tsc)性能提升2-5%。

不妨重新评估你长期搁置的迁移计划,选取现有代码库,询问 Claude 迁移流程如何。


相关链接