一批研究 COBOL 程式碼遷移的工程師發表了論文,提出把 AI 鎖進籠子。
論文標題很樸素:《Agentic Method for Deterministic Validation of Legacy Code Migration》。他們設計了一個叫 Locksmith Loop 的系統——名字取得精準,「鎖匠」,不是「大師」。不指望 AI 一次把活幹好,要的是把 AI 限制在一個確定性的框架裡,讓它只做它擅長的事。
但真正有意思的不是這個結論。是他們為什麼不得不這樣做,以及這樣做之後發生了什麼。
三個模組,AI 只碰一個
Locksmith Loop 的架構分三層。
第一層是 Migrator。COBOL 原始碼進來,Java 目標程式碼出去。這一步不用 LLM,用的是確定性 AST 轉換器——規則寫死,COMPUTE 變 BigDecimal.add(),PERFORM VARYING 變 for 迴圈。每次跑,每次結果一樣。TimByte 在 HN 上總結得精準:「他們把程式碼生成委託給確定性工具,模型只做輸入 fuzzing——讓它跟資料搏鬥,別碰語法。」
第二層是 Witness Search。這是 AI 唯一出場的地方。LLM 不斷構造輸入案例,企圖穿透程式的所有分支。每次跑完,覆蓋率分析器會檢查哪些分支還沒被觸達,然後 LLM 針對性地構造新輸入。這個過程循環往復,直到覆蓋率推不動為止。
第三層是 Oracle。COBOL 原版和 Java 遷移版同時跑同一個輸入,輸出必須逐位元一致。任何偏差都視為遷移失敗,回退修正。
為什麼要這樣設計?因為每一步都有明確的失敗模式。
Migrator 是確定性的,所以翻譯錯誤是可重現的、可追溯的。如果 COMPUTE 轉 BigDecimal 的精度出了問題,你會知道是轉換規則寫錯了,不是模型在某次推理中機率偏移了。
Oracle 是確定性的,所以驗證結果是二元的——對就是對,錯就是錯。不存在「看起來差不多」的灰色地帶。
Witness Search 是唯一非確定性的環節。但它的失敗模式是可控的——最壞情況是沒找到某個分支的觸發輸入,導致覆蓋率不夠。它不會產生錯誤程式碼,不會引入幻覺,不會在翻譯環節製造 bug。
這就是 Locksmith 的設計哲學:把 AI 放在它失敗後果最輕的地方。
當 AI 推不動了
Witness Search 不是萬能的。跑到某個分支邊界時,它可能會卡住。論文裡管這種情況叫 Locked Paragraph——一個阻止深入探索的條件,AI 構造不出能穿透它的輸入。
這是「鎖匠」名字的另一層含義:系統不指望 AI 能打開所有鎖。當 AI 推不動時,分析器會標記這個 Locked Paragraph,然後系統繼續推進其他分支。最終覆蓋率取決於 AI 能找到多少「鑰匙」。
論文報告了三個測試案例的結果。兩個開源 COBOL 程式,一個「內部生產級」程式。規模從 430 行到 4114 行。兩個開源程式達到了「幾乎完全覆蓋」。生產級程式達到了 91.90% 的分支覆蓋率。
91.90%。這個數字本身沒有太多討論價值,因為樣本太小。真正有價值的是剩下那 8.1% 意味著什麼。Locked Paragraph 是 AI 能力的邊界——它找到了 91.90% 的鑰匙,剩下 8.1% 的鎖它打不開。這些鎖在真實系統裡,很可能對應著最複雜的業務邏輯、最深的巢狀條件、最古老的 corner case。
連 bug 一起搬,是設計目標
架構還有一個反直覺的設計:遷移後的 Java 必須和原 COBOL 行為完全一致,包括 bug。
aldente0630 在 Hacker News 上澄清了這一點:「保留 bug 是明確的設計目標。」但這不意味著論文在鼓勵保留 bug——它只是承認了一個現實:在 legacy 遷移的世界裡,這叫 bug-for-bug compatibility。那些 bug 已經在生產環境跑了二十年,下游系統依賴它們的行為。修了一個 bug,等於修了一個「別人以為正常」的功能。
Hyrum's Law 在這裡完全適用:「對於有足夠多使用者的系統,任何可被觀察到的行為——無論規範裡寫了還是沒寫——都會有人依賴它。」
但 bug-for-bug 有一個前提:Oracle 對比的是行為,不是程式碼。你保留了 bug 的行為,但你沒有保留 bug 的可理解性。原來的 COBOL 老程式設計師知道那個 bug 為什麼存在——可能是某次緊急修復留下的,可能是某個業務規則變更的遺跡。但 Java 程式設計師看到這段「故意保留 bug 行為」的程式碼時,他們只會看到一段看起來像是寫錯了的程式碼,沒有任何上下文。
4KLOC 和 230KLOC
Hacker News 上的評論沒有討論架構設計,而是直接質疑了論文的規模。
pacaro 給出了真實世界的尺度:「IRS(美國國稅局)一家就有大約 160 個 COBOL 程式,平均每個 23 萬行程式碼。」
fock 的評論更具體,因為他試過:「我們用的程式碼裡內嵌組語,跨十幾個檔案、5 萬行。這個程式用了某個預處理器,我們測試的所有 LLM 完全不知道它是什麼——它們會編造它的功能。Gemini 2.5 甚至沒注意到有預處理器存在。」
這指向了 Locksmith Loop 的真正邊界。Migrator 是確定性 AST 轉換器,但 AST 轉換的前提是你能 parse 原始碼。如果原始碼裡有預處理器巨集、內嵌組語、CICS 事務指令、JCL 批次處理調度——這些不是 COBOL 語法的一部分,是環境的一部分。AST 轉換器不認識它們,也不會編造它們(這是優點),但它會直接失敗(這是問題)。
Zenst 補充了精度問題:「COBOL 以固定無捨入數學聞名。Java 的 0.1 + 0.2 等於 0.30000000000000004。除非全面使用 BigDecimal,否則遷移從一開始就是壞的。」論文用了 BigDecimal,這是對的。但金融系統的 COBOL 程式通常使用 packed-decimal(BCD 編碼),其精度和捨入行為與 BigDecimal 並不完全等價。Oracle 的逐位元對比會抓住這些差異,但修復它們意味著在 Migrator 裡寫越來越複雜的規則。
換句話說,Locksmith Loop 的確定性架構方向是對的,但它的工程複雜度會隨系統規模非線性增長。從 4KLOC 到 40KLOC,不是工作量乘以 10,是你要處理的邊緣案例、環境依賴、精度差異、非標準語法全部同時爆炸。
COBOL-in-Java
dragonwriter 在 HN 上點出了這個架構最深層的矛盾:「唯一現實的低錯誤率方案不是更複雜的 AI 用法,而是確定性轉譯。問題在於,這樣得到的是 COBOL-in-Java——跑起來正確,但維護起來是惡夢。」
確定性轉換的產物是:語法是 Java,靈魂是 COBOL。變數名、控制流、資料結構全部帶著 1960 年代的味道。PERFORM VARYING 變成了 for 迴圈,但變數的命名方式、資料的組織邏輯、錯誤處理的分支結構,全部原樣保留。
人類程式設計師做遷移時,會做一件 Migrator 不會做的事:重新理解系統的意圖,然後用新語言的慣用方式重新表達。這引入了風險(理解可能出錯),但這是唯一能產出可維護程式碼的方式。
Migrator 選擇了零風險路徑,但付出了可維護性的代價。你失去了 COBOL 老程式設計師,換來了 Java 程式設計師——他們看著這套程式碼,比老程式設計師還困惑。
matsemann 在 HN 上的第一句話就是這個意思:「以前至少還有幾個 COBOL 老程式設計師懂這套系統。現在沒人懂了。」你把一套沒人懂的老系統,變成了一套沒人懂的新系統。問題沒有解決,只是換了個語言。
論文沒說的東西
Locksmith Loop 的架構設計是對的。把 AI 限制在測試生成,把翻譯和驗證留給確定性程式碼——這可能是 AI 在嚴肅軟體工程中唯一可靠的使用方式。
但它迴避了一個更大的問題:遷移 COBOL 系統的真正瓶頸從來不是「翻譯一段程式碼」,而是理解這個系統在做什麼。二十年沒人碰過的程式碼,沒有文件,沒有測試,原開發者已退休。你連它應該產生什麼輸出都不知道,你怎麼驗證 Oracle 的輸出是對的?
論文假設 Oracle 是給定的——COBOL 原版能跑,能產生輸出,用來和 Java 版對比。但在真實場景裡,COBOL 系統可能已經跑不起來了,或者只能在一個無法複製的特定大型主機環境裡跑。Oracle 不是現成的,Oracle 本身就是一個需要重建的東西。
這不是 Locksmith Loop 的錯。這是一個驗證方法論文,不是遷移工程論文。但如果你正在認真考慮遷移 COBOL 系統,你需要知道的不是「AI 能做測試生成」,而是「你的 COBOL 程式還能跑嗎?」
參考來源
• Agentic Method for Deterministic Validation of Legacy Code Migration — arxiv • Hacker News 討論