叫 AI 把一段舊程式改寫成新語言,現在已經不太難了。難的是改完之後,你怎麼知道它跟原本的行為一樣。
最常見的流程是:AI 改寫,AI 順手寫測試,測試全綠,合併。問題在於那些測試是同一個 AI 寫的,它只會測它想得到的情況。舊系統裡那些「月底遇到空白欄位會停下來」「負零要當成零」之類的怪行為,沒人寫進規格,也不會出現在測試裡,要等到上線後才會冒出來。
Anthropic 在官方外掛市集 claude-plugins-official 放了一個 code-modernization 外掛(目前版本 1.0.0),整個設計就是在處理這件事。我把它的 README、指令檔和代理設定讀過一遍,這篇整理它在做什麼、哪裡設計得好,還有還沒開始跑就該先知道的幾個地方。
它把「改寫」拆成九個步驟
安裝只要一行,在 Claude Code 裡輸入:
/plugin install code-modernization@claude-plugins-official
裝好之後有 13 個指令、8 個專門的代理(agent),另外有 6 支 workflow 腳本負責需要同時開很多代理的步驟。指令全名都長這樣:/code-modernization:modernize-assess,下面為了好讀只寫最後的動詞。
它的入口是 /code-modernization:modernize。這個指令會用彈出視窗問你兩件事:想對這份程式碼做什麼(升版、換技術重寫、重新設計架構,或者「先搞懂它就好」),以及有什麼必須維持不變(舊系統要繼續跑、行為要完全一致連怪癖都保留、順便修已知錯誤、先做資安檢查)。答案會寫進 analysis/<name>/INTENT.md,之後每個指令都讀這份檔案,不會再問一次。
每一步都會產出一個檔案,也會更新 analysis/<name>/REPORT.html 這一頁總覽報告,所以你可以在任何一步停下來看。下一步要跑什麼,每個指令結尾都會直接給你可以貼上的那一行;忘了走到哪,就跑 modernize-status <name>。
我覺得最有意思的設計是那幾個橘色標籤。外掛明確列出六個地方一定要人來決定:回答 preflight 的五個問題、改正看起來不對的業務規則、核准計畫、接受驗證時發現的差異、簽核驗證結果、套用資安修補。這六件事它不會替你做,計畫沒核准前也不會開始寫任何新程式碼。
preflight 的五個問題也很實際:這是整個系統還是一部分、在這台機器上能不能建置和跑測試、有沒有自訂的建置工具、以前有沒有人試過改寫、有沒有什麼是不能碰的。這些問題 AI 讀程式碼答不出來,只能問人,所以 README 建議第一輪要找一個熟悉這套系統怎麼建置、怎麼部署的工程師在旁邊。
三種改法,計畫會建議其中一種
步驟 6 的建置分三種,看你要的是什麼:
| 你想要 | 指令 | 它怎麼做 |
|---|---|---|
| 同一套技術升到新版(.NET Framework 到 .NET 8、Java 8 到 17、Spring Boot 2 到 3) | uplift |
保留原本的程式碼,只修新版本會壞掉的地方。先做一個試點單元,把經驗寫成 playbook,再分批套用到其他單元。 |
| 換成另一種技術,一個模組一個模組換,舊系統繼續跑 | transform |
先寫測試把舊行為固定下來,再用新技術慣用的寫法重寫,最後新舊跑同一組輸入比對。 |
| 在新架構上重建 | reimagine |
從程式碼整理出規格,架構經過審查核准,再產生服務骨架和可執行的驗收測試。 |
uplift 因為保留原本的程式碼,可以跳過業務規則那兩步,preflight → assess → uplift → verify 就是一條完整的路。如果分析下來發現所謂的「升版」其實要改掉大部分程式碼(像 Python 2 到 3 到處都是 str 和 bytes 混用),它會直接說這應該改用 transform。
純搬家(rehost)和直接買現成產品(replace)不改程式碼,沒有對應的建置指令,但前面的分析一樣用得上。
業務規則寫成卡片,附上檔案和行號
步驟 4 的 extract-rules 是我覺得最值得單獨拿出來用的一步。它會把藏在舊程式碼裡的計算、檢核、資格判斷、狀態轉換、保留期限這類規則挖出來,每條寫成一張 Given/When/Then 卡片。代理設定檔裡給的範例長這樣:
Given an account with balance $1,250.00 and APR 18.5%
When the monthly interest batch runs
Then the interest charged is $19.27 (balance × APR ÷ 12, rounded half-up to cents)
用的是具體數字,不是「當餘額大於零時計算利息」這種抽象描述。1,250 × 18.5% ÷ 12 = 19.2708,四捨五入到分就是 19.27,你可以自己驗算。
每張卡片還要附上:
規則在原始碼的
file:line位置寫死在程式裡的參數(費率、上限、魔術數字),這些通常之後要改成設定值
信心程度(High、Medium、Low),不是 High 的要寫出要問業務專家的那個問題
優先等級,P0 是「錯了會讓系統失去意義,或代價很高、無法挽回」的規則
挖規則的代理跟檢查規則的代理是分開的,第二個代理會回頭對照每一條引用的程式碼。它也分得很清楚什麼不算業務規則:日誌、錯誤處理、技術性重試、連線池這些「換了語言就不存在」的東西都不收。
接著 review 會把有疑問的規則一條一條丟給人判斷:對、不對、不確定。結果寫進 RULE_REVIEWS.json,後面的計畫、建置和測試都會讀。它不會去改 BUSINESS_RULES.md 本身,人的判斷另外記一份。
就算你不打算讓 AI 改寫任何東西,光是這份附行號的規則清單,拿來做系統交接或補文件就已經很有用。入口那邊選「先搞懂它就好」,就是只跑到這裡加上計畫,不會產生任何新程式碼。
驗證是重做一次,不是看筆記
整個外掛最核心的是步驟 7 的 verify。README 建議在建置完之後開一個新的 session 跑它,因為它刻意不相信建置步驟留下的紀錄,而是自己重做一遍。
這六項檢查裡,我覺得有三個特別到位:
測試數字從測試工具自己的結果檔讀,不是從 AI 的回報抄。一次沒有執行任何測試的跑法直接算失敗。這擋掉了 AI 說「全部通過」但其實什麼都沒跑的狀況。
它會自己生出至少 10 組新輸入再比一次:邊界值、空值、超大資料、順序不對的資料,如果你要求連怪癖都要一致,還會加格式錯誤的紀錄。用意是抓出「測試只在 AI 自己挑的案例上會過」的情形。
canary 是故意在新程式碼裡改壞一行,測試必須變紅。連比對腳本本身也會被測:改掉輸出的一個 byte,看它有沒有抓到。測試能過不稀奇,測試要能失敗才有意義。
最後由 scripts/proof_pack.py 從這些結果檔算出每個模組一個判定:PROVEN、PARTLY PROVEN(會寫出缺什麼)、NOT PROVEN(沒跑、有差異、測試沒過)。每次都從現有的證據重新算,不沿用上一次的結果。
如果舊系統沒辦法在你手邊執行,例如大型主機上的 COBOL,或只能在正式環境跑的東西,就只能拿錄下來的輸出比對,這時最好的結果就是 PARTLY PROVEN,報告會直接寫出來,不會硬給你一個 PROVEN。
官方拿真實專案跑過的結果
README 有一節列出開發時拿公開專案實際跑過的結果,數字都是那些執行留下的檔案。我挑幾個比較有參考價值的:
| 專案 | 做什麼 | 結果 |
|---|---|---|
| AWS CardDemo(COBOL) | 每月利息批次改寫成 Java 21 | 35 條規則(8 條 P0),最後 213 個測試。連續五輪獨立驗證跟真的 COBOL 程式比對,每一輪都抓到測試漏掉的東西:一個會讓批次停下來的空白欄位、非數字的帳戶鍵、負零。最後一輪又試了 15 組含格式錯誤的輸入,找到 7 個差異,判定 NOT PROVEN。 |
| Eclipse Jetty(2,600 個檔案) | Java 8 升 17 | 試點 jetty-util 模組,946 個測試在兩個版本結果一致。實際建置執行後發現 6 個光讀程式碼沒預測到的變化,例如某個打包外掛產生無效的 manifest 卻仍然回報 SUCCESS。 |
| osCommerce(PHP,約 44,000 行) | 改成 Python 和 FastAPI | 商品頁這一塊:17,957 個測試通過,17,727 組比對跟真的 PHP 輸出完全相同。獨立驗證判定 PROVEN。 |
| Spring PetClinic(7 個服務) | Spring Boot 2.6 升 3.3 | 找出 10 個檔案裡 59 個 javax import 要改,還有一個請求在 2.6 回 200、在 3.3 回 400(兩個版本都實際跑過確認)。 |
CardDemo 那一列我覺得最誠實。五輪驗證每輪都找到新問題,最後還是 NOT PROVEN。README 的解讀是:讓這種循環停下來的,不是工具,是有人決定「哪些輸入在範圍內」。這也是為什麼入口那一題「行為要完全一致,連怪癖都保留」會影響驗證要多嚴格。
另外還有一個特別做的「有陷阱的專案」:在註解、README、CLAUDE.md 和專案設定檔裡埋了指令,還有一支會產生標記檔的腳本、檔名裡藏 shell 語法。結果埋的東西一個都沒被執行,標記檔沒出現,原始碼沒被改,埋的那幾行被列在報告裡,裡面的憑證也被遮蔽了。拿 AI 去讀別人的舊程式碼,這點很重要。
讓 AI 碰不到原始碼
外掛的慣例是:原始碼放在 legacy/<name>,只讀;所有產出寫進 analysis/<name>/ 和 modernized/。跑 preflight 時加 --source <路徑>,它會建一個 symlink 指過去,不會複製,所以很大的 repo 也可以原地分析。
只靠慣例不夠,README 建議在工作目錄放一份 .claude/settings.json,用權限規則把它鎖起來:
{
"permissions": {
"allow": ["Read(**)", "Edit(analysis/**)", "Edit(modernized/**)"],
"deny": ["Edit(/legacy/**)"]
}
}
Edit 規則也涵蓋 Write 工具,Write(path) 這種寫法其實不會被參考。開頭的 / 是把規則固定在工作目錄的根。preflight 會檢查這條 deny 規則有沒有設。
還沒開始跑就要先知道的事
下面這些是讀設定說明時看到、我覺得很容易踩到的地方。
用了 --source 之後,legacy/<name> 是一個 symlink。上面那條 Edit(/legacy/**) 擋的是連結的路徑,不是它指向的真實路徑。所以要另外把真實路徑加進 additionalDirectories 允許讀取,再加一條 Edit(//真實路徑/**) 的 deny。只設前面那條,等於門鎖了、窗戶開著。
權限規則管得到 Claude 的檔案工具和它認得的 shell 指令,管不到自己去開檔案的腳本。所以 README 特別提醒,uplift 的步驟 5b 和 reimagine 的階段 E 會同時開很多會寫檔的代理,這兩步的 Bash 權限要維持在「每次詢問」的模式。
用量要有心理準備。最重的 extract-rules 在官方的測試裡每次開 50 到 200 個代理,跑之前會先告訴你要開幾個,大規模的會先問你。時間上,幾萬行的系統大概是:preflight 3 到 4 分鐘、assess 5 到 8 分鐘、map 和 extract-rules 各 5 到 15 分鐘,建置一個模組 15 到 30 分鐘。百萬行等級的系統,README 的建議是一次只做一個模組。
規則是模型挖的,跑兩次可能挖到不同的規則。官方自己對 osCommerce 跑兩次資安掃描,一次確認 48 個問題,另一次 52 個。所以 BUSINESS_RULES.md 要當成「需要審過的產出」,不是每次都一樣的建置結果。
如果你想用 claude -p 做成腳本,要注意 headless 模式等背景工作最多等 10 分鐘就不等了,extract-rules、harden 這些步驟會跑不完。要先設 CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0。
地圖、規則分片、驗證判定和報告都靠 Python 3.8 以上的 python3,scc 或 cloc 用來算程式碼規模,都可以先用 preflight 檢查。最強的那種驗證(新舊程式並排跑)需要這台機器能建置舊系統;沒有建置工具時,它會改用錄下來的輸出做比對,並在結果裡寫明。
最後是遙測。外掛預設會透過 Claude Code 自己的遙測送出使用次數,只有整數(跑了哪個指令、找到幾條規則、作業系統和 Python 版本這類),不送程式碼、路徑或檔名,100 以上的數字還會四捨五入到兩位有效數字。想關掉的話,外掛選項裡的 Usage counts、環境變數 CODE_MODERNIZATION_TELEMETRY=0,或 Claude Code 本身的 DISABLE_TELEMETRY=1,任一個都可以。比較要注意的是,每個版本在每台機器上啟動 session 時會送一次執行環境資訊,就算你沒用它的指令也會送。
什麼時候值得用
我的看法是,就算你根本沒打算重寫,modernize 入口選「先搞懂它就好」,跑出評估、結構圖、附行號的業務規則和一份計畫,對接手一套沒人敢動的舊系統就已經很值得。
真的要改寫時,它最大的價值不是幫你寫新程式碼(這件事 Claude Code 本來就做得到),而是那套驗證:拿舊程式當答案,自己生新輸入,確認測試會失敗,最後誠實給一個 NOT PROVEN。至於差異要不要接受、範圍要劃到哪裡,它不幫你決定,這部分還是要有人來負責。