当一个从未写过 Rust 的人决定用 AI 工具在一个月内迁移一个包含 10 万行 TypeScript 代码的复杂系统时,这听起来像是一个鲁莽的实验。然而,这个故事的主角是 vjeux——Prettier 代码格式化器的核心作者,一个在代码基础设施领域拥有深厚功底的工程师。他在 2025 年底发起的这个实验,最终产出了一个行为等效但性能提升 3.5 倍的 Rust 实现,通过率高达 99.96%。但这个过程的艰辛程度远超他的预期,也揭示了当前 AI 编程工具在代码迁移场景下的深层局限。
差分测试:不懂 Rust 时的唯一信任锚点
vjeux 面临的核心挑战非常直接:他不懂 Rust。这意味着他无法通过阅读生成的代码来验证其正确性。传统代码审查的方法在这里完全失效。那么,他如何确保迁移后的系统行为与原版完全一致?
他的答案是差分测试(Differential Testing)。具体做法是构建一个自动化框架,同时运行 TypeScript 原版和 Rust 新版,让它们处理完全相同的输入,然后比较输出是否一致。在这个案例中,他运行了超过 230 万次随机生成的战斗场景来验证 Pokemon Showdown 系统的行为等效性。这种方法本质上将代码迁移问题转化为测试问题 —— 只要测试覆盖足够全面,就能捕捉到任何行为偏差。
这个策略的有效性取决于测试生成的质量。230 万次随机战斗意味着系统需要能够生成多样且有意义的输入场景,同时测试框架必须能够准确捕获和比对两种实现的输出差异。对于游戏逻辑这类具有复杂状态和规则的系统,差分测试确实提供了一个相对可靠的验证机制。但它也有盲区:某些边界条件和特定输入序列可能从未被测试覆盖,而这些恰恰是生产环境中可能触发的场景。
陷阱一:改进冲动与代码重复
在 HN 的讨论中,一个程序员分享了他让 Claude 移植 libgdx 游戏的经历,这个教训与 vjeux 的遭遇形成了有趣的呼应。他描述了一个典型场景:他要求 Claude 将一个函数移植到新的平台,但 Claude 决定「改进」代码 —— 它将 createStartButton() 拆分为创建和布局两个独立的方法。这个看似合理的重构引入了三个 bug:布局重叠(getY () 与 getY () - getHeight () 的混淆)、子元素未正确设置大小、变换原点的更新导致动画异常。
这个问题揭示了当前 AI 编程工具的一个深层特性:它们被训练为生成「好」的代码,而「好」在训练数据中往往意味着更模块化、更简洁、更符合最佳实践。但在代码迁移场景中,「好」的定义应该是「与原版行为完全一致」。改进现有代码是一个需要理解全部上下文和约束的复杂推理任务,而 AI 工具倾向于快速给出看似合理的答案,却无法真正理解生产环境中那些看似冗余或奇怪的代码背后隐藏的业务逻辑和约束。
vjeux 在迁移过程中遇到了类似问题。Claude 在不同文件中创建了两个不同版本的「move」结构,它们各自都能独立编译,但整合在一起时却无法正常工作。这种重复结构的产生,正是因为 AI 工具在没有一个统一的架构视图的情况下,试图为每个独立模块生成「最优」的代码,而忽视了模块间需要保持一致性这个约束。
陷阱二:上下文压缩与状态丢失
当处理 10 万行代码这样的超大规模项目时,上下文窗口的限制成为一个无法回避的问题。Claude Code 在长时间会话中会触发上下文压缩(compaction),将之前的历史信息压缩或丢弃以腾出空间给新的内容。这个机制在普通对话中影响不大,但在代码迁移项目中可能是灾难性的。
问题在于,代码迁移需要维护一个全局的架构视图:哪些类型已经被定义、哪些接口已经实现、不同模块之间的依赖关系是什么。当上下文被压缩后,这些信息可能丢失,AI 工具可能会忘记之前做出的决策,导致重复定义类型、不一致的实现风格,或者违背之前确立的架构约束。
vjeux 观察到的一个现象是:「Claude 工作一段时间后,似乎总是停下来重新总结情况。」这正是上下文压缩的信号 ——AI 需要重新构建对项目的理解,但这个过程中必然会丢失一些微妙的上下文信息。一个可能的缓解策略是在项目关键节点强制 AI 重新读取所有核心文件,但这会增加时间和 token 成本。
陷阱三:优化悖论 —— 越优化越慢
代码迁移的一个常见动机是性能提升。Rust 以其零成本抽象和内存安全著称,将 TypeScript 代码移植到 Rust 理论上应该能带来显著的性能改善。但 vjeux 遇到的情况却有些讽刺:Claude 在优化方面的尝试大多以失败告终。
他尝试让 Claude 进一步优化已经移植的代码。Claude 制定了一个看似合理的计划(对于一个从未接触过 Rust 的人来说甚至无法判断计划的质量),花费了一天时间实现多个优化,但最终没有任何优化真正改善运行时性能,有些反而让情况变得更糟。
这个问题的根源在于,Rust 的性能优化需要对内存模型、所有权系统、生命周期等底层概念有深入理解。Claude 虽然能够生成语法正确的 Rust 代码,但它对这些概念的理解是统计性的、基于训练数据的,而非真正理解为什么某种写法比另一种更高效。当它试图应用「最佳实践」时,往往是在用 JavaScript 或 TypeScript 的思维模式写 Rust 代码,结果是得到了可以编译但性能更差的实现。
这给我们的教训是:AI 辅助迁移应该分为明确的两个阶段。第一阶段是忠实移植,目标是行为等效,不追求任何优化。第二阶段才是优化,但这个阶段需要由真正理解目标语言的工程师来指导和验证,而非完全依赖 AI。
实用迁移检查清单
基于 vjeux 的实战经验和其他开发者的讨论,我们可以提炼出一套 AI 辅助代码迁移的实用指南。
首先,在开始任何迁移工作之前,必须建立完善的差分测试框架。这个框架应该能够运行原版和新版代码,处理相同的输入,并准确报告任何行为差异。测试覆盖率越高,迁移的可信度就越高。对于关键系统,考虑使用 property-based testing 来自动生成大量边界条件输入。
其次,明确约束 AI 工具的「改进」冲动。一个有效的方法是在系统提示中强调「复制优先」(copy-first)的原则,明确告知 AI 在迁移完成之前不要试图优化或重构代码。可以建立一个 CLAUDE.md 文件来记录这些约束,并在每个会话开始时提醒 AI。
第三,实施显式的状态恢复机制。在长时间会话中,定期要求 AI 重新读取关键文件,确保它对项目状态的理解是最新的。对于大型项目,考虑将代码库分解为更小的模块,逐个迁移并验证,而不是试图一次性迁移整个系统。
第四,严格分离迁移和优化阶段。将迁移项目拆分为两个明确的目标阶段:第一阶段只追求功能等效,第二阶段才允许进行性能优化。优化阶段需要有熟悉目标语言的工程师参与,不能完全依赖 AI 的判断。
第五,建立多层次的验证体系。除了差分测试,还应该包括静态分析(检查类型错误、未使用代码等)、单元测试覆盖,以及关键路径的手动审查。AI 生成的代码可能在功能上正确,但在可维护性、可读性方面存在隐患,这些需要人工介入来识别和修正。
边界与红线
vjeux 的案例告诉我们,AI 辅助代码迁移已经能够完成令人印象深刻的工作,但这并不意味着它已经可以无风险地应用于所有场景。当前技术更适合那些具备以下特征的项目:有完善的测试覆盖、迁移目标明确(行为等效而非功能增强)、有足够的预算来覆盖 token 消耗和人工验证成本。
对于真正关键的系统,AI 应该被视为一个强大的「初级开发者」—— 它可以在指导下完成大量工作,但需要资深工程师的审查和指导。完全信任 AI 生成的代码,尤其是在你无法阅读目标语言的情况下,是一种危险的做法。代码库最终是需要人来维护的,如果一个团队中没有能够理解目标语言的成员,那么迁移后的代码库将变成一个无人能读懂的「黑箱」,这是技术债务的极端形式。
AI 辅助代码迁移是一个强大的工具,但它目前更适合作为加速现有工程实践的手段,而非替代品。理解其边界、规避其陷阱,才能真正发挥其价值。
资料来源:Hacker News 讨论(46765694)