Mistral 翻车两次的迁移记,验了我六月的土办法

Hello,我是飞飞。
六月我拿 agent 把 7000 多行的老 Java 项目一天迁成 Kotlin 上了线,事后写了一套验证门的打法。写的时候我心里其实打鼓:这套土办法是摸着我那点代码量趟出来的,换个规模还灵不灵,我不知道。
昨天 Mistral 发了篇官方复盘,他们用 agent 帮一家欧洲能源运营商迁油藏模拟器,40000 行 Fortran 77。
我把复盘逐条对了一遍,先说结论:路子撞了,而且他们替我把两个极端的坑都趟了一遍。这篇就把两边的账摊开对。
他们迁的是什么
Fortran 77,1977 年标准化的科学计算老语言,说人话就是编程界的活化石。
官方复盘里列的几个特征,条条都是祖传味:状态全放在 COMMON blocks 里,说白了就是全班共用一块黑板,谁都能上去写;变量隐式定型,I 到 N 开头的名字自动是整数,拼错一个变量名不会报错,会静默新建一个变量;变量名最长六个字符;流程靠 GOTO 跳来跳去。
更麻烦的是外围:没有测试套件,没有集中文档,资料散在旧 PDF 和代码注释里,原作者已经离职。整个代码库三十万行,这次只迁核心的四万行。
这套设计的麻烦在于,任何一行代码都可能往黑板上写全员共享的状态,你翻译任何一段,都得先弄清别的角落谁在什么时候写过它。官方复盘头一回让 agent 一对一硬翻翻车,根子就埋在这儿。
我六月面对的那个 529 行祖传 XSS 过滤器,搁这四万行面前,连个零头都算不上。但你要是往下看会发现,难的从来不是代码多大,是没人能告诉你「迁完之后行为还是原来那个行为」。
两次翻车记
说实话,复盘里最值钱的是失败段,厂商复盘难得写得这么实。
头一次,全自主。每条 Fortran 子程序派一个 agent 独立翻译,跑了一周。产物能跑,官方复盘的原话是「像用 C++ 语法重新抄了一遍 Fortran」:COMMON blocks 一比一变成全局 struct,GOTO 控制流原样保留。能编译,能出数,但该重构的一点没重构。
另一回,纯 agent 协作。每个模块配 planner、coder、tester、reviewer 四种角色,质量确实上去了,但复杂度压垮了它们:碰到一个 bug,试几轮修复,然后停在那儿。停下来的样子是:任务干挂着,没了下一步,谁也不再来动它。原话是「attempt a few fixes, and stall」。
走通的版本换了个思路:保留那套 agent 工作流,加回人类检查点。人负责审架构、审 PR,最关键的是 agent 卡死的时候有人来解卡。官方复盘的结论原话是:带人工审查门的结构化工作流,打赢了全自主和纯手动的两个极端。
这句结论我看着眼熟。我六月那场迁移里,agent 把一个叫 isLiability 的字段悄悄并进了别的 class,名字凭空消失,编译通过测试也过,满屏 diff 看不出来,靠的是流水线里一组专门唱反调的对抗校验 agent 在报错时捞回来。它还会顺手把空指针判断「优化」掉、把分支悄悄改岔,且从不主动报告,一个任务来回改几十次是常态。
两个样本,一个四万行一个七千行,各自摔出来的结论是同一句:谁出的活谁自己审,等于没审;人在门后面,这活才交得出去。
还有个第三方参照。Bun 团队用同类工作流把整个项目从 Zig 移植到 Rust,七十五万行,每个文件配两个 reviewer agent,十一天从第一个 commit 走到 merge,原有测试 99.8% 通过。规模再大一个量级,走的还是「翻译的归翻译、审查的归审查」这个分法。
最硬的一处同构
两边的打法里,有一招几乎一模一样,值得单独拎出来。
我六月是这么干的:项目里那个 529 行的祖传过滤器,没人敢保证重写后正则行为一致,我让 AI 先拿旧版跑一批输入,抓下 19 条「黄金输出」,钉成断言。新版本任何一点行为漂移,测试立马变红。当时我不信「能编译」,是有缘故的:前文那桩字段消失,就发生在编译和测试全绿的版本里。编译器只管语法契约,不管行为漂没漂。
Mistral 是这么干的:迁移前先建一套数值对账台,给 Fortran 代码插桩,把关键变量的中间状态导出来,比如 RHOG = 42.71834 这种带好几位小数的快照;C++ 那边建测试框架加载这些检查点,逐个做数值断言。官方复盘说,数值相等是「容易验证且有说服力的证据」,并把这一步定为任何代码现代化项目的头一步。
隔了三个月、隔着完全不同的代码量,两边独立收敛出了同一个动作:不信任「能编译」,只信「行为钉死」。区别在精度上,他们比我狠:我只钉了最终输出,他们连中间过程的关键点都设了检查站,中途哪一步数值飘了当场就报。
我打算抄回来的三招
对照下来,有三招是我六月没做、这次记下要抄的。
头一招,文档化先行。Mistral 把文档化做成了独立阶段:先用自研 parser 把整棵调用树画出来,说白了就是一张谁调用谁的族谱,然后派了一百多个 agent 照着这张族谱,从叶子节点往上逐个写文档、开 PR,还有 reviewer agent 挂定时循环专门审新文档,审出问题自动排修复任务。
文档的原料也有讲究:散在旧 PDF 里的资料,靠文档库加 Mistral OCR 让 agent 自己去拉。复盘把「把这些散落资料统一整理到代码旁边」列为最大的副产品之一,迁一次代码,白捡一份整理好的文档。我六月直接从翻译开跑,规模小跳得过,四万行跳不过。
另一招,中间点检查站,就是他们比我狠的那一处。这一招治的病很具体:中途漂和终点错是两种病,只验终值,中途的病会带到最后才发作。
还有一招,切模块的经验值:单模块控制在一万行以内,按调用树切自包含的块。我六月是按「一次只动一个变量」控制步子,他们是按行数封顶切模块,两句话说的是同一件事,他们的数字能直接抄。
先说清楚人家的前提
复盘自己也交代了边界,这几条得原样带上。
这四万行代码是自包含、可运行的,官方复盘原话承认这是「有利起点」;如果你的老代码依赖外部系统、跑不起来、逻辑没有任何文档,难度要再上一个台阶,这篇复盘没讨论。
复盘里说的有利起点,我六月其实也占着:老项目有测试、能本地跑,我还能起真 MySQL 和 Redis 做冒烟,真机那道门当年抓出过两个 bug,都是单看代码和跑测试都发现不了的那种。反过来想,要是当年那个项目没测试也起不来,我那套验证门的头一块砖就没了,整套打法得重新设计。这是读完这篇我最大的后怕。
另外,整个项目的总周期、具体用的哪些模型、客户是谁,原文都没披露,而且这是厂商自述没有第三方验证,数字你当口径看。
轮到你了:你手上迁过的最老的代码是哪年的?当时怎么验证迁完没迁坏?评论区报个年份,我想看看这条时间轴能拉到多长。顺带说一句,Mistral 那套数值对账台真不难搭,你下次迁任何有输出的老代码,都可以先搭这个再动手。