Vibe coding 時,LLM 並不真的「聽話」。很多人因此去找更強的咒語,想拴住這匹脫韁的野馬。

但 prompt 裡寫一句「請遵守 Hexagonal Architecture」,效果其實很有限。它比較像在工地口頭交代一句「請按圖施工」:有提醒,沒有保證。最後沒歪掉,很多時候也不是因為工人真的照做,而是旁邊還有人在監工。

你真的相信 AI 會乖乖聽勸嗎?1

工程上真正有約束力的規矩,不能只停在說明、默契或設計哲學。它得能被系統檢查;必要時,能拒絕、阻斷,甚至直接讓流程停下來。

所以,談 LLM 時代的工程護欄,先抓住一個判準就夠了:這條規矩能不能被機器檢查,並在必要時改變系統的下一步。

一、先分清鷹架、驗證器與護欄

談工程護欄時,最容易混在一起的,其實是三種不同功能的東西:

  • 鷹架 (scaffold):幫語言模型比較容易走在對的方向上。它提高做對的機率,減少誤解與偏航,但不能保證出錯時一定攔得住。
  • 驗證器 (verifier):回答「這份產出有沒有過關」的機制,例如測試、型別檢查、靜態分析器、形式驗證器。
  • 護欄 (guardrail):當語言模型做錯、做偏、做越界的事情時,能阻止它繼續往下走的機制。

差別的關鍵,在於:檢查結果能不能改變系統的下一步。 很多時候,護欄不是另一種神祕工具,而是把驗證器接進流程控制之後扮演的角色。測試如果只是存在於 repository 裡,它提供的是訊號;一旦接進 CI,成為合併或部署的阻斷條件,它才同時成為護欄。

像 Design by Contract、Specification by Example、static analysis、property-based testing、sandbox、deployment gate、人工批准,放進來看都會更清楚:有些是在幫你對齊,有些在幫你檢查,有些則真的會把下一步擋下來。

到了 LLM 時代,重要的不是把這些名字講得更完整,而是把重要約束盡量往可檢查、可判定、可阻斷的一端推。2

二、Spec 與架構原則在做什麼

Spec 和架構原則都能提供約束,但它們處理的不是同一件事。

Spec-driven development (SDD) 處理的是對齊。它把需求、變更、設計、任務與實作串成同一條工作鏈,讓人和 AI 協作時,不必只靠聊天上下文去維持一致。較強版本的 SDD,會把 spec 提升成 source of truth,往下展開 plan、tasks,甚至進一步驅動 implementation。3 較輕版本的 SDD,則是在 code 之前先固定一層持續存在的 spec,讓需求、變更與實作之間有穩定的承接點。4

它的價值不在於把文件寫得更厚,而在於後續實作、驗證與變更追蹤都能對著同一組座標工作。規格一旦能持續存在、被引用、被對照,後面的檢查就不是從零開始。這能減少上下文漂移,也能降低人和語言模型各講各話的機率。

架構原則處理的,則是邊界。它固定依賴方向、責任分配與模組之間的接觸方式,避免修改時一路穿透整個系統。

像「domain 不應依賴 infrastructure」、「application service 不應直接碰 UI adapter」、「core module 不得 import vendor SDK」這類規則,如果只存在於設計文件、團隊默契或 code review comment 裡,主要仍是在幫人理解系統。把它們寫成依賴規則、匯入限制、architecture lint、架構測試與 CI 阻斷條件之後,邊界才真的固定下來。像 ArchUnit 這類工具做的,就是把依賴方向、分層限制與循環依賴寫成可自動執行、一有違規就 fail 的規則。5

Spec 固定的是座標;架構原則固定的是邊界。兩者都很重要,但它們做的都還是同一類事:讓系統比較不容易歪掉。

三、真正握著 pass / fail 的,是驗證器

到了產出這一層,真正決定過不過關的,通常還是型別系統、schema、assertions、static analysis、property-based testing、design by contract、formal verification 這類能給出硬性判定的機制。它們檢查某個性質成不成立;不成立,就直接給出 fail signal。接進自動化流程之後,也最容易變成真正的護欄。

語言模型可以幫忙讀工具輸出、整理告警、推測可能原因,但還不適合坐在正式裁決者的位置上。近年的 benchmark 顯示,LLM 在某些程式語意推理任務上確實進步了;但碰到更深層、多步驟的靜態分析推理,表現仍然不穩。比較保守的工程結論是:現階段,語言模型可以協助 static analysis workflow,但還不能把它當成正式 static analyzer 的替代品。6

至於 LLM-as-reviewer,比較適合放在初步快篩的位置。它能幫忙擴大檢查面,例如指出明顯臭味、補測試點子、列出可能漏掉的邊界情況,或整理其他工具的輸出。最近也有研究指出,它能讓檢查更聚焦、更少幻覺。7 這些都有價值,但它的位置仍然是初步快篩,不是最後仲裁。

不過這裡還有一層更容易被忽略的問題。驗證器的強度,不只要看用的是哪個框架,也得看規則是誰寫的。以 property-based testing 為例,如果 property 是人先定義好,再交給框架展開大量案例,它仍然是很強的驗證器;但如果連 property 都交給 LLM 自己發明,就會有球員兼裁判的問題:它測的到底是原本預期的行為,還是自己最容易實現的性質?上游若把 property 的定義權一併交出去,驗證強度就可能被偷換。8

換到 property-based testing、design by contract 和 formal verification,也還是同一件事。語言模型可以協助補測試、補契約條件9、整理驗證器回饋、加速迭代修正;但最後握著裁決權的,仍然是 verifier。10

四、當 agent 能行動,護欄就得前移

不過,事情一旦從「產出」走到「行動」,風險模型就變了。

如果 agent 只會補全程式碼,失誤大多還停留在寫錯;如果 agent 還能讀寫檔案、改設定、跑腳本、觸發部署,甚至反覆自我修補,失誤就變成做錯事。前者主要看驗證,後者則要先看權限。

所以,前面幾節談的那些編譯期、測試期、驗證期約束,到了這裡都還不夠。agent 一旦有了工具權限,真正重要的防線就會往前移到執行邊界本身。此時要管的,不只是它寫得對不對,而是它能不能碰、可以碰到哪裡、出了事是否留痕、哪一步需要停下來等人批准。

這時真正重要的護欄,就是權限範圍控管、沙箱隔離、稽核記錄、速率限制、部署批准,以及其他執行期的限制條件。

Agent 一旦從「生成建議」走到「執行動作」,問題就不再只是怎麼驗證產出,而是:哪些限制必須在它動手之前就先寫硬。

五、哪些規矩值得寫硬

到了這裡,問題已經不是要不要再多一些原則,而是:哪些限制如果不寫硬,代價會高到不能接受。

判斷這件事,我會先看四個面向:

  1. 錯誤代價:出錯的成本高不高。
  2. 行動權限:agent 的行為會不會直接改變外部世界。
  3. 審計需求:事後需不需要清楚追溯,誰在什麼條件下放行。
  4. 一致性成本:這條規矩如果只靠默契,能不能跨人員、跨時間穩定維持。

這四個維度上的壓力越大,這條規矩就越不適合只留在文件、會議紀錄或 code review comment 裡,越應該被翻成測試、schema、lint rule、approval gate 或權限控制。

反過來說,如果錯誤代價低、行動權限小、審計需求弱,而且團隊本來就能穩定維持一致,那它可能就不需要被工具鏈執法。留在慣例、review 或較弱的規格層,很多時候就夠了。

所以,真正要問的不是規矩能不能寫硬,而是:它值不值得被寫硬。

這裡還是要小心另一個問題。當規矩大量轉成可由機器裁決的限制,很容易出現新的激勵錯置:大家開始為了過關而寫測試,為了通過檢查而補 schema,最後得到的未必是更好的工程品質,而可能只是更僵硬的流程。

護欄不是越多越好。好的護欄,是把高代價風險卡住,同時不要把系統變成只會通關的官僚機器。

結論

所以真正要問的,往往不是這條規矩寫了沒有,而是當它出了事,有沒有機制真的在監工。


  1. 關於大語言模型 nondeterministic 的問題,詳見〈AI 會寫程式之後,軟體工程真正變貴的是什麼〉一文。 ↩︎

  2. 本文主張:對 LLM 而言,穩定的約束力主要來自外部、可機器檢查、且可嵌入流程阻斷的機制,而不是語言模型是否「理解規矩」。 ↩︎

  3. 參考 Spec Kit 對 Spec-Driven Development 的自我定義與 workflow 說明:specification 不再只是 scaffolding,而是變成 executable source,能沿著 constitution → specification → planning → tasks → implementation 的鏈條依序往下執行。 ↩︎

  4. 參考 OpenSpec 的 README、concepts 與 getting started 文件:OpenSpec 強調 fluid not rigiditerative not waterfall,以 specs/ 作為 current behavior 的 source of truth,以 changes/ 保存每次 change 的 proposal.md、delta specs、design.mdtasks.md,並沿著 proposal → specs → design → tasks → implement 的順序推進。 ↩︎

  5. 根據 ArchUnit 官方使用手冊的說法,ArchUnit 可檢查 package/class 依賴、layer、slice 與循環依賴,並以一般 Java 單元測試框架自動執行 architecture 與 coding rules。 ↩︎

  6. CoRe benchmark 專門評估 LLM 在靜態分析型程式語意推理上的能力;結果顯示,模型雖能處理部分任務,但在更深層、多步驟的語意推理上仍有明顯限制。本文據此採取較保守的工程判斷:現階段不宜把 LLM 視為正式 static analyzer 的替代品。 ↩︎

  7. SGCR (specification-grounded code review) 的核心主張是把 code review 建立在明確、人工撰寫的規格上,以提升回饋的相關性與可信度;這支持較保守的結論:spec-grounded review 值得投入,但現階段更適合放在初步快篩,而非最後仲裁者的位置。 ↩︎

  8. 許多由 LLM 產生的測試程式,常常有球員兼裁判的問題。最明顯的例子,就是濫用 mock 湊出綠燈通過的假象。因此,在 “Use Property-Based Testing to Bridge LLM Code Generation and Validation” 這份研究中,也承認由 LLM 產生 property,是他們這份研究的弱點之一。 ↩︎

  9. Beyond Postconditions” 這篇研究關注的是:LLM 能不能從程式或既有規格中推導出可供自動驗證使用的 formal contracts,而且範圍不只限於傳統 postconditions。它的價值在於「協助起草與補全契約條件」:能幫忙加快 formal specification 的建構,但不等於模型已經可以取代 verifier 來正式裁決。 ↩︎

  10. Vericoding benchmark 與 Faria 等人的 Dafny annotation 研究都支持同一件事:當流程採用「生成 → verifier 檢查 → 回饋 → 再生成」時,真正掌握裁決權的仍是驗證器 (verifier),而不是語言模型本身。 ↩︎