【深度分析】四萬行 Fortran 搬到 C++:Mistral 的 AI 舊系統遷移實驗

把舊程式換成新語言,和把它改造成容易維護的系統,是兩件不同的事。Mistral 分享了一次科學計算軟體遷移:先建立數值驗證與文件,再調整 AI agent 的分工及人工審查方式。
原文摘要
本文為原文的中文重點整理,非全文翻譯。原文由 Carlo Antonio Patti 與 Rasul Alakbarli 於 2026 年 9 月 9 日發表。成果與流程均依 Mistral 的專案自述整理,並非本站獨立重現的測試。
案例範圍與語言轉換的難點
客戶是一家歐洲能源業者,其儲層模擬器以 Fortran 77 編寫,涉及大量物理與數值計算。專案起點沒有測試套件,也沒有集中整理的文件;團隊希望遷移至 C++,並支援 PETSc 等現代科學計算框架。
Fortran 77 的共享狀態、隱含型別及控制流程,讓問題超出語法轉換。COMMON 區塊可讓不同程序共享全域資料;在隱含型別機制下,變數拼字錯誤也可能未立即成為編譯錯誤。若直接逐行改寫,這些結構未必會隨語言更新而消失。
原文以氣體密度計算示範差異:舊程序讀寫共享陣列,C++ 版本則把相關資料集中為明確型別,透過函式參數輸入並回傳結果。這種改寫涉及資料與介面的重整,不只是把原有敘述換成另一套語法。
先讓新舊結果可以比較
團隊先建立 parity harness,也就是新舊系統的數值一致性驗證工具。驗證不限於最終結果,還包含客戶儲層工程師指定的關鍵中間狀態。
做法是讓 Fortran 輸出狀態快照,再由 C++ 測試框架讀入檢查點;另以 Skill.md 指引 agent 使用這套工具。原文示範了一次 RHOG 值為 42.71834 的執行,將該值作為對照新模組的參考。
這提供了模組完成遷移的具體檢查方式。不過,原文沒有公布足以獨立重現整個專案的程式、完整測試集合或誤差規格,因此這裡只能呈現團隊描述的驗證方法,不能延伸成所有輸入均已證明等價。
整理文件,再交給 agent
既有知識分散在舊 PDF 與程式註解。團隊用自製解析器建立呼叫關係樹,再透過 Vibe CLI 啟動超過一百個 agent,結合文件工具及 Mistral OCR 讀取相關資料。
文件工作由呼叫樹的葉節點向上進行,agent 為各部分整理文件並提出 PR;另一個定時執行的審查 agent 檢查新 PR,必要時安排修正。這些文件被放回程式碼附近,讓後續遷移工作有可用的脈絡。
三次調整:全自主、固定分工、人工介入
第一輪讓各 agent 獨立遷移 Fortran 子程序。團隊表示結果可以運作,但 COMMON 區塊多半只是變成全域 struct,GOTO 式控制流程也被保留,沒有完成預期的架構現代化。
第二輪改為規劃、實作、測試及品質審查等角色合作。程式品質有所改善,但 agent 面對複雜錯誤,嘗試修正數次後仍可能停滯。
最後採取人工操作的模組化流程:agent 負責實作、測試與審查,人類在卡關時介入,並保留 PR 的人工核准。團隊與領域工程師一起辨識獨立模組,原文給出的經驗規模是每個模組低於約一萬行 Fortran。
工作順序是先提出 C++ 架構,由儲層工程師審查;核准後拆成任務,再反覆實作與測試,最後由人類要求修改或同意合併。
四萬行成果不等於整套系統完成
原文指出,第一個 sprint 涵蓋三十萬行系統中的四萬行核心功能。案例有一項重要的有利條件:舊 Fortran 系統本身完整且可以執行,因此可作為新版本的對照基準。
若系統依賴外部服務、缺乏可執行的舊版本,或包含沒有文件的物理模型,還會有本文未處理的困難。原文也未提供整體工時節省、成本對照或全系統完成遷移的證據。
作者將經驗歸納為三點:先建立數值對照、先整理文件,以及在此類規模與複雜度下保留結構化流程與人工審查。這些是該專案團隊的結論,不應讀成 AI 已能無人接手所有舊系統遷移。
技術補充:把遷移流程拆成可執行的步驟
以下是依上述方法整理的通用工程操作說明,並非原文翻譯,也不是 Mistral 公開的內部 SOP。範例、檔名與驗收清單均為教學示意;原文未公開的指令、提示詞、檔案格式與誤差門檻,不在此冒充已知資訊。
第一步:保存一個能重跑的舊版本
先不要要求 agent 翻譯程式。第一個交付物應是「別人也能跑出相同基準結果」的執行紀錄:舊程式的版本、編譯器及參數、輸入資料、執行方式與輸出位置。若相同輸入每次結果不同,要先釐清亂數、平行計算或環境差異,否則後續無法區分遷移錯誤與基準本身的不穩定。
選擇測試資料時,可先準備一個最小正常案例,再加入邊界與代表性案例。最小案例讓錯誤容易定位;代表性案例檢查接近實際工作負載的行為。這不是數量越多越好,而是每份輸入都要知道在檢查哪一種情況。
驗收方式: 從乾淨的環境按紀錄執行,能重現基準結果。若舊系統尚不能執行,這一步應停下來修復,不能先把 agent 的新輸出當成正確答案。
第二步:把「結果相同」拆成可檢查的條件
數值模擬不只看程式有沒有退出或最終檔案是否存在。可以請領域工程師指出重要狀態,再在舊程式輸出這些狀態,作為新程式的對照。例如輸入處理完、物性計算完、一次迭代完,各設一個可辨識的檢查點。
一筆檢查點需要的不只是數字,還包括它屬於哪個案例、哪次迭代、哪個陣列位置、使用什麼單位。否則新舊程式都輸出同名變數,卻可能是在比較不同時刻或不同網格位置。
教學範例,非 Mistral 的資料格式:
{
"case": "small-grid",
"checkpoint": "after-properties",
"step": 2,
"field": "density",
"index": 0,
"unit": "kg/m3",
"value": 1000.0
}
這個格式示範的是如何讓數值帶著上下文;1000.0 不是該專案的結果。新版本的測試應依同一組識別欄位找到對應資料,再比較值,而不是只比較輸出檔的行序。
浮點數是否要求逐位相等,必須另外定義。不同編譯器、運算順序或平行化可能影響末位,因此通用測試常使用以下形式:
abs(new - reference) <= absolute_tolerance
+ relative_tolerance * abs(reference)
絕對容許誤差控制接近零時的差距,相對容許誤差則依參考值尺度調整。門檻應依物理意義、精度需求與資料尺度制定;NaN、無限值、形狀不一致或缺少檢查點,需另列規則,不能因一般數值比較沒有報錯就放行。原文並未公開其實際容許誤差,因此這個公式只是通用說明,不代表團隊採用它。
驗收方式: 測試報告能指出「哪個案例、哪個階段、哪個元素」不符合條件。可以另外製作一份故意改錯的測試副本,確認比較器確實會拒絕它,而不是永遠顯示通過。
第三步:畫出呼叫關係,也盤點共享資料
呼叫關係告訴你 A 會呼叫 B,但不一定告訴你 A 和 C 透過全域狀態交換了什麼。尤其遇到 COMMON 區塊,遷移單位不能只根據函式名稱劃分:還要知道誰寫入共享陣列、誰依賴它,以及執行順序是否影響結果。
可為每個候選模組準備一份短文件,記錄用途、輸入/輸出、呼叫對象、共享狀態、重要單位與對應測試。資料來自程式、PDF 或領域工程師時,要留下出處;文件與程式互相矛盾的部分先標成待確認,不讓 agent 用猜測補完。
原文採取從葉節點向上整理文件。理解這種順序的方法是:先弄清較少依賴其他程序的功能,再處理組合它們的上層。不過,一般程式可能有遞迴或循環依賴,呼叫關係不保證真是一棵樹;遇到循環時,需把相關節點一起分析。
驗收方式: 接手者不必把整個舊系統讀完,也能說明這個模組的責任、狀態依賴與驗證入口。
第四步:先審查新介面,再派發程式任務
以共享陣列為中心的子程序,改成 C++ 後不應只換成一個同樣巨大的全域物件。設計階段可以先列出:哪些資料只讀、哪些會被修改、哪些應由呼叫端持有、哪些結果由函式回傳。這些決定會直接影響測試是否可以單獨執行。
例如物性計算模組,可考慮以明確的資料物件表示查表資料,以參數傳入狀態,回傳計算結果。重點不是一定要面向物件,而是讓資料流與副作用可見。單位、索引範圍、錯誤處理與邊界行為也應在介面審查時確認。
原文的模組規模經驗是低於約一萬行 Fortran;這不是通用硬上限。一個較小但依賴混亂的模組,也可能比行數較多、邊界清楚的模組難遷移。
驗收方式: 領域工程師確認物理意義沒有變,軟體審查者確認介面與資料責任清楚,再將設計拆成任務。
第五步:每個 agent 任務都附輸入與完成條件
「把這段程式搬到 C++」不足以定義完成。可交給 agent 的任務包應包含已核准介面、相關舊程式、依賴文件、對照案例,以及允許修改的範圍。完成條件則至少分成兩類:行為是否符合基準,以及結構是否符合新設計。
教學用任務包清單:
- 要遷移的功能與不包含的功能。
- 已核准的函式介面、資料型別與單位。
- 必須通過的基準案例及中間檢查點。
- 可修改的檔案,以及禁止更動的參考資料。
- PR 應提供的測試結果、已知限制與待人工確認事項。
讓 agent 修改實作,不代表讓它任意修改驗收標準。若參考值需要修正,應單獨審查原因;不能為了測試通過,直接把舊系統輸出改成新系統的答案。
驗收方式: 每個 PR 都有可檢查的結果,不以「agent 說已完成」代替測試紀錄。
第六步:數值測試與架構審查分開做
測試通過只能說明已檢查的案例符合條件,不能保證維護性。反過來,類別切分漂亮也不能證明物理計算正確。因此需要兩張不同的檢查清單:一張檢查結果與中間狀態,另一張檢查共享狀態、控制流程、介面、副作用與文件。
測試失敗時,可先找最早出現差異的檢查點,再回查該階段的資料。這比只盯最終結果,更容易縮小錯誤範圍。常見檢查方向包括索引轉換、型別精度、初始化、單位與邊界條件;這些是通用排查項目,不是該案例已公開的錯誤清單。
如果 agent 多次嘗試仍無進展,人類介入的目的是補足錯誤診斷、領域知識或任務切分,而不只是再按一次重試。PR 合併前,保留測試失敗與修正的紀錄,可讓後續模組避免重踩同一問題。
第七步:模組合併後,重新跑整體流程
單一模組通過對照,不代表與其他模組接起來就正確。整合時還要檢查呼叫順序、資料生命週期及狀態交接,重新跑代表性案例。若專案還有性能或記憶體目標,也要另外量測;數值一致性本身並不證明速度變快。
最終交付包可以包含:可執行的新版本、介面文件、對照測試、整合結果、已遷移與尚未遷移的範圍,以及仍依賴舊系統的部分。對照 Mistral 案例,四萬行是第一階段的範圍,不能在交付說明中抹掉其餘二十六萬行。
- 原文:Modernizing complex legacy code with AI agents(Carlo Antonio Patti、Rasul Alakbarli,Mistral,2026-09-09)