
單一 COBOL 檔案把 AI 該知道的都說了,唯獨漏掉要命的那一項,所以我先畫了地圖。
引發這一切的那一行 COBOL 只有三個詞,而每一個都對我撒了謊。
COMPUTE WS-RESULT = WS-WIRE-AMOUNT - TRN-LIMIT. 我當時在為一家中型銀行的電匯子系統打造示範用程式資產,而這行命運般的程式碼,就藏在一支名為 WIRETXN 的程式裡。它看起來像大一新生也能移植的算術:從金額減去限額,寫出結果。如果你把那單一個檔案交給任何現代模型,要求輸出 Java,它大約四秒就能給你乾淨、可編譯、單元測試全過的 Java。它會把 TRN-LIMIT 定型為 long。而在第一筆正式電匯上,它就會把損毀的位元組寫進正式環境資料庫。
我之所以知道,是因為 TRN-LIMIT 並不是 long。它是一個封裝十進位 COMP-3 欄位,定義處隔了三個檔案,其正式執行時的解讀,取決於一支完全不同的程式所設定的旗標,而該旗標又由凌晨兩點執行的批次作業排程。這些在 WIRETXN 裡全都看不見。含有這段危險 COMPUTE 的檔案,並不包含任何讓它變得危險的事實。
正是這個缺口,讓我打造了 CodeGraph,而這篇文章講的是我在到達那裡的路上走錯了什麼。我一開始確信問題出在轉譯品質。我錯了。問題是模型看不見它必須看見的東西,而我花了一段時間向自己證明:再多的「給它更多脈絡」也修不好這件事。
那看起來安全、其實並不安全的 COMPUTE
我先親手把這次電匯變更繪製出來,才敢信任任何工具去做,而一次正確遷移必須知道的事實,正好是九項。
其中三項住在 WIRETXN 裡,對單一檔案讀者而言確實可見。WIRETXN 使用 TRN-LIMIT 於那道 COMPUTE(第 33 行)。它匯入一本名為 CBACCT,僅依名稱,位於第 13 行。它觸發一次 UPDATE,對象是 DB2 資料表 ACCOUNTS(第 37 行)。文字視窗工具能看見這三項。若事實僅止於此,天真移植就沒問題。
另外六項才是會傷人的。TRN-LIMIT 宣告為 PIC S9(9)V99 COMP-3,位於 CBACCT.cpy 第 11 行,意思是封裝十進位,也就是 BigDecimal(在 Java 裡),絕不是 long。就在它下方,TRN-LIMIT-ALPHA REDEFINES TRN-LIMIT,把同一六個位元組以原始文字覆疊在該欄位上。第三個欄位 LIMIT-TYPE-FLAG 在執行期決定這兩種解讀中哪一種是正式生效的。那個旗標由名為 LIMITSET 的程式寫入,也再次由名為 BATCHUPD 的夜間批次作業寫入。而 JCL 作業 NIGHTLY 在 02:00 以電匯作業的前置作業執行——整套程式資產裡,唯一記錄「設定旗標」與「執行轉帳」先後順序的地方,就在這裡。

六項事實。每一項都為真,每一項都是承重結構,而每一項從真正做計算的那個檔案望出去,在結構上都看不見。當我這樣把它們排開時,讓我不安的不是天真移植錯了,而是 天真移植根本無從得知自己錯了。 它讀了被交給它的那一個檔案,而那一個檔案對那六項要命的事實保持沉默。
含有危險那一行的檔案,並不包含任何讓它變得危險的事實。那不是轉譯缺陷。那是穿著轉譯缺陷外衣的檢索失敗。
為什麼我不再試圖把上下文視窗做得更大
我的第一直覺,和此刻每個人的直覺一樣——我也想誠實地說,我追過一陣子:就再多給模型一點。
這套推論聽起來無懈可擊。若失敗是因為模型只看見一個檔案,那就連 copybook 也餵進去。把碰過那個旗標的程式也餵進去。把 JCL 也餵進去。上下文視窗如今大得驚人,而且每個季度還在變大,所以答案肯定是別再吝嗇,把整片鄰近程式碼倒進提示裡。我真心預期這會奏效;對玩具範例來說,它多少也確實有效——因為當你已經知道該貼上哪六個檔案時,你其實已經親手解掉了真正的問題。
裂縫就在這裡。要把正確脈絡餵給模型,我得先知道哪一段脈絡才是正確的。而知道 TRN-LIMIT 的型別由寫在 BATCHUPD 裡的旗標決定,並由 02:00 的 JCL 作業排定順序——這不是把 WIRETXN 讀得更用力就能抽出來的。你只能靠已經追過相依圖才能得到。上下文視窗並不會告訴你,該把什麼放進上下文視窗。 我一直在用答案去回答問題。
接著數字把這點釘死了。這些銀行真正在跑的程式資產不是六個檔案。它們是一百萬到一千萬行 COBOL,有時更多;整個產業仍有約 2,200 億行在正式生產中運行(產業統合分析,2025)。一次真實的電匯變更,其遞移閉包可能是四十個檔案,或四百個。那永遠塞不進上下文視窗——今天不行,三年後出貨的那一版模型也不行——因為程式資產長得比視窗快,而視窗從來就不是真正的約束。約束是:在一千萬行裡,知道這次變更碰觸的是哪四十個檔案,並證明你找到的是全部四十個,而不是三十八個。
更大的上下文視窗,是對一個我已不再問的問題給出更好的答案。問題不是「模型能不能裝下更多程式碼」,而是「哪一段程式碼,以及你如何證明那就是全部」。
這次重新框定,正是 CodeGraph 不是轉譯器的全部理由。我刻意不貼上 COBOL 再交回 Java。地圖才是產品,而轉譯是地圖存在之後,任何工具都能做的下游用例。我打造的是底下的理解層。在已出貨的測試夾具上,程式資產被解析成一張含 47 個節點與 70 條邊的型別化知識圖;「變更的影響」就是一次圖遍歷——該變更所觸及一切的遞移閉包——每條邊都帶著它來自的 file:line。刻意做成無聊的純 Python 圖工作,熱路徑裡沒有模型,因為我需要它的不是聰明,而是完整與可重現。同一測試夾具進,同一閉包出,每一次都一樣。
我反覆對自己說成一條規則。代理人給建議,程式碼做決定。 示範裡可選的語言層——用白話英文回答關於閉包的問題的那部分——預設關閉,並以金鑰閘控。價值不依賴它。價值在檢索與證明,而這兩者都不是模型能力。
天真視角究竟刪掉了什麼?
我特別在示範裡做了一個開關,好讓我親眼看那六項事實消失——因為在看見它發生之前,我並沒有完全相信這次失敗。
勾選「Naive AI context view」,圖就塌縮成單一原始檔,外加變更周圍的一窗行數——那正是文字視窗工具餵給模型的東西。讀成 9/9 的面板掉到 3/9。檔內三項事實維持亮起。另外六項變灰、安靜下來:COMP-3 型別、REDEFINES 覆疊、控制旗標、它的兩個跨模組寫入者,以及 02:00 的 JCL 前置作業。一道紅色橫幅用應用程式自己的話點出後果:只拿到三項可見事實時,模型會輸出 long TRN_LIMIT 並損毀資料庫。

我想在這裡小心一點,因為這正是創辦人容易過度銷售的地方。9 對 3 的結果,量測對象是 已出貨的合成電匯測試夾具——一套我為這次示範親手撰寫的程式資產,正是為了讓真實相依集合已知,召回數字成為有標註的真實量測,而不是感覺。它不是對你的 COBOL 的保證。天真視圖是模擬,不是正式 z/OS 管線。圖在記憶體中,底下是 SQLite,不是正式圖平台。我打造合成銀行,是因為我無法在倫理上向你展示真實的一家,也因為已知標準答案,才是唯一誠實地說「圖拿到全部九項、單一檔案拿到三項」的方式。
但失敗的形狀不是合成的,而那才是要緊的部分。那個 COMP-3 欄位——其型別在別處決定——、由批次作業設定的旗標、只存在於 JCL 裡的順序——這些是四十年銀行程式資產的尋常質地,不是古怪邊角案例。當大約 70% 到 80% 的主機現代化專案未能達成目標時(產業統合分析,2025),我不再認為是因為轉譯步驟很糟。轉譯步驟沒問題。它被餵進的是一張把六項最重要事實裁掉的圖。
有證明,才算數
我最自豪的功能,是那個承認自己做不到什麼的功能——而直到一場合規對話替我重新框定,我才真正體會到這一點。
工程師要的是正確遷移。監管者要的是不同、也更難的東西:證據。在 DORA 下,銀行欠一份 ICT 資產清冊。在 SOC-2 下,它欠變更控制收據。這兩者都不是模型說「相信我,我找到相依了」就能滿足的。它們需要完整度證明——一份說明工具實際解析了多少程式碼庫的陳述,更重要的是,對它無法解析之處的誠實標旗。於是我做了一道完整度閘門。測試夾具裡每一次 PERFORM、CALL、COPY,以及每一次 DB2 參照,都必須解析到圖中的真實節點,否則標成「needs review」。不許任何東西默默消失。
在測試夾具上,那道閘門解析了 34 次參照中的 33 次,覆蓋率 97.1%。它無法解析的那一次,是一支名為 DISPATCH 的程式,它做動態 CALL WS-PROGNAME——一個執行期才算出的目標;任何靜態剖析器都跟不上,因為目的地要到程式跑起來才知道。而那裡正確的行為不是猜測,而是升起一面旗,寫著「這裡需要有人看一眼」,並把它留在報告裡。

那次被標旗的 DISPATCH 呼叫,是整次打造裡我最喜歡的東西——我是認真的。一個能解析 97%、並精確告訴你哪 3% 做不到的工具,比一個宣稱 100% 卻把缺口藏起來的工具更有價值,因為被藏起的缺口,正是損毀電匯棲身之處。完整度閘門產出可匯出的「Codebase Topology and Completeness Report」——一份 JSON 與可列印 HTML,含節點與邊摘要、帶 file:line 出處的各模組閉包、召回結果,以及附時間戳的標旗項目。那份產物才是重點。它是你可以交給監管者、下季重跑、並因確定性而得到相同答案的東西。
我寧願出貨一個承認自己有洞的數字,也不要一個把洞藏起來的更圓數字。被標旗的動態 CALL 不是示範的弱點。它就是示範。
這也是不會過時的那一部分。一個完美的模型——從不幻覺出任何一行 Java——仍然無法向監管者證明它檢索了哪些相依。它仍然無法靜態跟隨一個執行期才計算的 CALL,因為那是程式碼的性質,不是讀者的性質。出處與完整度,是你圍繞模型打造之系統的性質,不是靠把模型放大就能解鎖的能力。
你動手的順序
圖給我的最後一樣東西,是我起初根本沒打算打造的:一道安全的動手順序。
一旦你有了完整的相依拓撲,就可以依糾纏程度替每支程式打分。我用一個樸素公式,以耦合對 COMP-3 陷阱、JCL 關鍵度與未解析呼叫加權,把測試夾具裡的十四支程式排成絞殺榕抽取順序。風險最低的程式最先抽,神級程式最後。在測試夾具上,AUDITLOG 排在第 1 名,風險分數為零,因為它沒有耦合,也沒有任何東西依賴它必須正確。那是安全的起點。我們一直擔心的 WIRETXN 程式排在第 11 名,帶著它那一個 COMP-3 陷阱與其 JCL 關鍵度。DISPATCH 帶著未解析的動態呼叫,排在第 12 名。而 ACCTMGR——一切都倚靠的神級程式——最後才抽,排第 14 名,風險分數 15。

我沒預期自己會像現在這麼在乎排序。但這是同一課的第三次。你能從哪裡安全開始,是拓撲上的事實,不是規劃會議裡爭辯的意見。 一支盯著百萬行程式碼的團隊,其實並非在爭怎麼轉譯一段文字。他們無止盡、昂貴地爭論的是:從哪裡開始,以及先碰錯東西會壞什麼。那是圖的問題,而圖每一次執行都以同樣方式回答。
抽取順序、完整度閘門、影響閉包——它們是同一物件的三個視角。檢索真正的切片,證明它是完整切片,並依風險替切片排名。這三件事都不是轉譯問題,也都不是更聰明的模型能解的。
我反覆回到的那個問題
我開始對每一個看到的 AI 現代化推銷——包括我自己的——只問一個問題,而它悄悄成了我唯一信任的問題。
不是「它能不能寫出好的 Java」,因為答案幾乎總是能,而且幾乎從不重要。更難的問題,是 TRN-LIMIT 那一行教我的那一個:它能不能在此刻證明檢索了哪些相依,而那份證明能否扛住一個想讓它失敗的監管者。若工具無法用 file:line 出處向我展示閉包,也無法誠實告訴我它解不了什麼,那麼輸出再流暢也無關緊要。那是語法漂亮的猜測,而我正好看過那種猜測把 long 套在封裝十進位欄位上,然後伸手去碰資料庫。
產業花了十年把轉譯步驟做得更好,同時 70% 到 80% 的專案仍未達目標(產業統合分析,2025);我認為那是因為風險從來就不在轉譯步驟。風險住在拓撲裡,住在六項看不見的事實裡,住在凌晨兩點設定的旗標裡。若你想看圖回收那六項事實、再誠實標出它做不到的那一項,示範在這裡:veriprajna.com/zh-Hant/demos/legacy-cobol-modernization。
若你寧可看它跑起來,也不想聽我描述,這裡是整套端到端運作。
我不再相信下一版模型釋出就是打通這些遷移的關鍵。更大的視窗能裝更多程式碼;它不知道該裝哪一段,也無法證明自己找全了。這在我敲下剖析器第一行時就為真,我想在我用來打造這套的模型退役很久之後也仍為真。地圖從來就是難的那部分。我們一直盯著轉譯,只因為那是我們知道怎麼打分的部分。


