AI 的文件生成:為什麼註解跟程式對不上
斷層一:上下文窗口的局部視野
AI 生成文件時,看到的不是你整個專案,而是你餵給它的那一段內容。這個限制有兩層:技術上的上下文窗口長度,以及實務上的取樣策略。
技術上,雖然現代模型的上下文窗口已經很大,但「大」不等於「全」。一個中型專案動輒數十萬行,不可能全部塞進去。所以實務上一定是片段式處理:一次看一個檔案、一個函式、一個模組。這就產生一個結構性問題:函式的行為往往由它所在的類別、它呼叫的依賴、它被呼叫的方式共同決定,而這些資訊分散在片段之外。
舉個例子。一個名為 process() 的方法,單看實作可能只是把輸入丟進佇列。AI 根據這段程式碼,會寫出「將資料加入處理佇列」這樣的註解。但如果你看到它的呼叫端,會發現呼叫前必須先呼叫 validate(),否則會拋出例外;如果你看到佇列消費端,會發現它預期的是特定格式的訊息。這些「隱含契約」不在單一函式的實作裡,AI 看不到,自然寫不出來。它寫的文件沒有錯,但也不完整——而不完整在某些情境下,跟錯誤一樣危險。
這帶出一個重要觀念:文件的本質是「跨片段的知識」。如果一個資訊只存在於單一函式內,程式碼本身通常已經表達得夠清楚,不太需要額外註解。真正需要被記錄的,往往是那些「散落在多處、靠人類記憶維繫」的隱含規則。而這恰恰是片段式 AI 生成最不擅長處理的部分。
斷層二:語意漂移與時間差
第二個斷層是時間。AI 生成文件是一個「快照」行為:它在某個時間點,根據某個版本的程式碼,產生一份描述。但軟體是活的,程式碼會重構、參數會增減、行為會調整。文件如果沒有同步更新,就會開始漂移。
漂移通常不是一次劇烈變化造成的,而是很多次小修改累積而成。今天改個變數名稱,明天加個參數,後天調整錯誤處理邏輯。每一次修改,工程師都覺得「這個改動很小,註解應該還算對」。但累積三個月後,註解跟程式碼的距離可能已經大到完全對不上。
AI 在這個過程中扮演什麼角色?它不會主動偵測漂移。它不會在你改程式碼時跳出來說「嘿,這段註解過期了」。它只會在你下次請它生成文件時,根據當下的程式碼重新產生一份——但前提是有人記得要請它重新生成。如果團隊沒有把「文件同步」變成流程的一部分,AI 生成只會製造更多「曾經正確」的文件,而這些文件比從來沒有過更難處理,因為它們看起來像是有人維護過。
這裡還有個更細緻的問題:註解本身也是一種程式碼,但它不受編譯器保護。變數改名,編譯器會報錯;函式簽章改變,呼叫端會編譯失敗。但註解寫錯,沒有東西會報錯。這種「無保護」的特性,讓註解的漂移可以無聲無息地發生。
斷層三:AI 的「合理化」傾向與幻覺
第三個斷層,跟 AI 的生成機制有關。大型語言模型的本質是「根據上下文,預測下一個最合理的詞」。這讓它非常擅長產生「讀起來合理」的內容,但也意味著它傾向於補全,而不是承認不知道。
當 AI 看到一個函式,但缺乏某些資訊時,它有兩條路:一是說「這裡資訊不足,我無法確定」,二是根據命名慣例和常見模式,推測一個「合理的」答案。在實務上,它經常選擇後者。這不是惡意,而是訓練目標使然——模型被獎勵產生連貫、有幫助的輸出,而不是頻繁地說「我不知道」。
於是就出現了所謂的「幻覺文件」:它描述了一個不存在的參數、一個從未被拋出的錯誤碼、一個實際上不存在的設定選項。這些內容不是隨機亂寫,而是「根據常見模式推測出來的合理內容」。這讓它們特別難被抓到,因為它們符合讀者對這類系統的預期。
舉個具體例子。假設有個函式叫 fetchUser(id, options),實作裡只用了 options.timeout。AI 生成文件時,可能根據常見的 HTTP client 模式,自動補上 options.retries、options.headers、options.cache 等說明。這些選項在程式裡根本沒被讀取,但文件寫得有模有樣。讀者照著文件傳了 retries: 3,程式不會報錯(因為它被忽略了),但行為跟預期不同——這種「靜默失敗」是最難除錯的。
斷層四:註解語言與程式語言的語意鴻溝
第四個斷層比較抽象,但很重要:自然語言和程式語言的表達能力不對等。
程式語言是精確的、可執行的、有明確語意的。自然語言是模糊的、依賴語境的、充滿歧義的。當我們用自然語言描述程式行為時,必然會丟失一些精確性,也會引入一些程式裡不存在的概念。
AI 生成文件時,是在做「程式語言到自然語言」的翻譯。這個翻譯過程會遇到幾個問題。第一,有些程式概念在自然語言裡沒有精確對應。比如「這個函式是純函式,沒有副作用」,用自然語言寫起來很囉唆,但程式裡一個 const 或 pure 標註就講清楚了。第二,自然語言會引入程式裡沒有的因果關係。AI 可能會寫「因為要先驗證,所以這裡先檢查」,但程式裡可能只是碰巧把驗證放在前面,並沒有因果設計。第三,抽象層次難以對齊。程式碼可以在不同抽象層次間跳躍,文件卻需要維持一致的敘事層次,AI 常常在這兩者之間抓不到平衡。
這個鴻溝不是 AI 獨有的問題,人類寫文件也會遇到。但 AI 的問題在於,它太擅長產生流暢的自然語言,以至於模糊和歧義被包裝得很漂亮,讀者不容易察覺自己讀到的是一個「簡化過的、可能失真的」描述。
斷層五:缺乏可執行的驗證回路
第五個斷層,也是最實務的一個:文件沒有測試。
程式碼有單元測試、整合測試、靜態分析、型別檢查,這些機制共同構成一個驗證回路,讓錯誤在早期就被抓到。文件沒有這種東西。你寫了一段註解說「這個函式會回傳使用者物件」,沒有任何工具會去驗證這句話是否為真。文件的正確性,完全依賴人工審查,而人工審查在時間壓力下,往往是最先被犧牲的環節。
AI 生成文件讓這個問題更嚴重,因為它降低了產生文件成本,卻沒有降低驗證成本。以前寫一份文件要花兩小時,工程師會比較謹慎;現在 AI 三十秒產出一份,工程師容易抱著「先有再說」的心態,跳過仔細檢查。於是文件產量上升,但平均品質下降,而驗證資源並沒有同步增加。
這形成一個惡性循環:文件越多、越不可靠,工程師越不相信文件,越不願意花時間維護,文件就越不可靠。要打斷這個循環,關鍵不是「不要用 AI 生成」,而是「讓文件也進入驗證回路」。這點我們在下一節會詳細談。
三、從「生成」到「對齊」:可落地的改善策略
理解了五個斷層之後,改善方向就比較清楚了。核心觀念是:不要追求「一次生成就正確」,而要建立「持續對齊」的機制。以下是四個我認為最實用的策略。
策略一:以程式碼為唯一事實來源
第一個策略聽起來很基本,但影響深遠:在流程上明確規定,程式碼是唯一的事實來源(single source of truth),文件是衍生物。
這句話的實務意義是:當文件跟程式碼衝突時,預設程式碼是對的,文件是錯的。這聽起來像是廢話,但很多團隊的實務操作剛好相反——他們把規格書當成事實,程式碼只是「實作」,於是當兩者不一致時,會去爭論「到底是文件寫錯還是程式寫錯」。這種爭論很耗時,而且常常沒有結論。
把程式碼定位為事實來源之後,文件生成的目標就變了:不是「創造真相」,而是「描述真相」。這個定位轉變會影響很多設計決策。比如,與其讓 AI 自由發揮寫一段說明,不如讓它從程式碼中萃取結構化資訊(函式簽章、型別、拋出的例外),再用模板組裝成文件。結構化資訊是從程式碼直接解析出來的,出錯空間小很多;自然語言的部分則留給人類補上「為什麼」和「使用情境」。
另一個實務做法是:把文件生成放進建置流程。每次 CI 跑的時候,順便重新生成 API 文件、比對是否有變更、如果有變更就開一個 PR 或發出通知。這樣文件就變成「程式碼變更的副產品」,而不是「另外一件事」。這聽起來很工程師思維,但正是這種思維能解決漂移問題——因為它把同步的成本降到接近零。
策略二:建立文件與程式的同步機制
第二個策略是具體的同步機制設計。這裡有幾個層次可以考慮。
層次一:註解就近原則。能在函式旁邊寫的註解,就不要放到遠端的 Wiki。就近註解的優點是,修改程式碼的人自然會看到它,降低「改了程式忘了改文件」的機率。AI 生成註解時,也應該優先產出「與程式碼同居」的形式,而不是另外產生一份獨立文件。
層次二:可執行文件。這幾年很紅的概念,對 API 文件特別有用。把文件裡的範例程式碼變成可執行的測試,每次 CI 都跑一遍,確保範例真的能跑。OpenAPI、Swagger 這類工具已經支援某種程度的驗證;更進一步的做法是把文件範例直接當成整合測試的來源。這樣文件寫錯,測試就會紅燈。
層次三:變更偵測。用工具監控「程式碼變更」與「文件變更」的關係。比如,當某個公開函式的簽章改變,但對應的文件沒有更新時,發出警告。這類工具不一定現成,但可以用簡單的腳本做到:解析程式碼的 AST,萃取公開介面,比對文件中的描述,找出不一致的地方。這聽起來很土炮,但對於核心模組來說,效果很好。
這三個層次的共同精神是:不要依賴人的記憶力,要依賴機制。人會忘記,機制不會。AI 可以幫助產生文件,但「確保文件跟程式一致」這件事,必須由流程和工具來保證。
策略三:用測試與靜態分析守住底線
第三個策略是善用既有的驗證工具。雖然文件本身沒有測試,但文件所描述的內容,很多是可以被測試的。
舉例來說,如果文件說「這個函式在輸入為 null 時會拋出 InvalidArgumentException」,那就寫一個測試來驗證這件事。如果文件說「這個 API 回傳的 JSON 包含 id、name、email 三個欄位」,那就寫一個測試來檢查回傳結構。這些測試同時扮演兩個角色:驗證程式行為,也驗證文件描述。當程式改變導致測試失敗,工程師會去修程式或修文件——無論哪一種,兩者又對齊了。
靜態分析也可以幫上忙。型別檢查器(如 TypeScript、mypy、Psalm)可以在某種程度上確保「文件描述的型別」與「程式實際的型別」一致——前提是文件是從型別註記生成的。這也是為什麼近年來「型別即文件」的風氣越來越盛:與其寫一段自然語言說「這個參數是字串」,不如直接在程式裡標註 string,然後讓工具生成文件。型別是機器可驗證的,自然語言不是。
對於 AI 生成的文件,還有一個實用的技巧:要求 AI 同時產出「可驗證的斷言」。比如,不要只寫「此函式會驗證輸入」,而是寫「此函式在輸入為空字串時拋出 ValidationError」——後者是可以被轉換成測試的。這種寫法逼 AI 把模糊的描述具體化,也讓人類審查時更容易抓到錯誤。
策略四:提示工程與上下文工程的優化
第四個策略回到 AI 本身。雖然我們說「不要期待一次生成就正確」,但好的提示和上下文設計,確實可以大幅降低錯誤率。
上下文方面,關鍵是「給對的資訊,而不是給全部資訊」。與其把整個檔案貼進去,不如精選幾個關鍵片段:目標函式、它的呼叫端、它的依賴、相關的測試。這些片段共同構成「行為的完整圖像」,比單一函式更能讓 AI 理解隱含契約。如果工具支援,也可以提供專案層級的慣例說明(例如「本專案所有錯誤都繼承自 AppError」),讓 AI 不要亂猜。
提示方面,有幾個原則值得遵循。第一,明確要求標註不確定性。告訴 AI:「如果你不確定某個行為,請標註『待確認』,不要推測。」這可以抑制幻覺。第二,要求引用來源。請 AI 在每個描述後面附上「依據哪一行程式碼」,這樣人類審查時可以快速核對。第三,限制輸出範圍。與其讓 AI 自由發揮寫一整篇,不如指定格式:「只描述參數、回傳值、拋出的例外,不要寫使用範例。」範圍越窄,出錯空間越小。
還有一個常被忽略的點:區分「描述性文件」和「規範性文件」。描述性文件是「程式現在做什麼」,這種適合 AI 生成,因為它可以從程式碼推導。規範性文件是「程式應該做什麼」,這種不適合完全交給 AI,因為它涉及設計決策和業務規則,需要人類判斷。把兩者混在一起,就會出現「文件寫的是一個理想行為,程式實作的是另一個現實」的落差。
四、工具與流程的實踐建議
講完策略,我們來談談實務上怎麼落地。這一節偏向操作面,適合準備在團隊裡導入 AI 文件生成的人參考。
導入 AI 文件生成的檢查清單
在開始用 AI 生成文件之前,建議先確認以下幾件事:
這份清單不是要擋住 AI 導入,而是要確保導入之後不會產生「看起來很專業但沒人敢用」的文件。
團隊協作中的角色分工
AI 文件生成不是「工程師按一個按鈕」這麼簡單,它涉及幾個角色的協作。
撰寫者(通常是工程師)負責提供正確的上下文、審查 AI 產出的內容、補上 AI 看不到的隱含知識。這裡的關鍵心態是:把 AI 當成「初稿產生器」,而不是「最終答案」。初稿可以快,但定稿需要人。
審查者(可能是 tech lead 或資深工程師)負責檢查文件與程式的一致性,特別是跨模組的契約。這種審查不應該變成逐字校對,而應該聚焦在「高風險描述」:錯誤處理、權限、資料格式、版本相容性。
工具維護者(可能是 DevOps 或平台團隊)負責建立同步機制、執行文件測試、監控漂移。這個角色常被忽略,但沒有它,前面所有策略都無法持續。
還有一個隱形角色:文件的使用者。他們是最終的驗證者。如果團隊有管道讓使用者回報「文件跟實際不符」,這些回報就是最有價值的改進線索。很多團隊沒有這個管道,於是小錯誤永遠不會被發現,直到某個新人踩坑。
五、結語:文件不是裝飾,而是契約
回到最初的問題:為什麼 AI 生成的文件,經常跟程式對不上?
答案不是「AI 不夠強」。即使是最先進的模型,只要它只看到片段、只做一次性生成、只被要求「產生看起來合理的內容」,它就必然會產出與現實有落差文件。這是結構性問題,不是能力問題。
真正的解法,是把文件從「裝飾品」重新定位為「契約」。契約的意思是:它描述了雙方的權利義務,而且可以被驗證、被執行、被追究。程式碼是實作方,文件是介面方,兩者之間的契約必須一致。當契約不一致時,不是「文件錯了改文件」或「程式錯了改程式」這麼簡單,而是要建立機制,讓不一致被及早發現。
AI 在這個新定位裡的角色,不是「取代人寫文件」,而是「降低產生初稿的成本,讓人可以把精力放在更有價值的地方」——放在定義契約、驗證契約、解釋設計意圖。這些是 AI 短期內難以取代的,因為它們需要對業務脈絡的理解、對取捨的判斷、對未來變化的預期。
所以,如果你正在考慮用 AI 生成文件,我的建議是:用它,但不要只用它。用它來加速初稿、用它來發現你懶得寫的細節、用它來保持文件的基本覆蓋率。但同時,把審查、同步、驗證這三件事做好。這三件事做好了,AI 就是助力;做不好,AI 只是幫你更快地製造技術債。