AI 的程式審查:為什麼它看不出架構問題
這個模組在三個月前為什麼被拆出來,以及拆出來之後又被誰偷偷依賴回去;
這個「共用工具」套件為什麼逐漸變成一個誰都依賴、誰都不敢改的垃圾場;
這個新加入的 feature flag 為什麼會在不知不覺中,讓兩條本該獨立的業務流程產生隱性耦合。
這些資訊不在程式碼裡,或者說,不只在程式碼裡。它們存在於 commit 歷史的脈絡、團隊的設計決策紀錄、issue 討論、以及資深工程師腦中的 tacit knowledge(默會知識)裡。AI 審查工具拿不到這些,也就無從判斷。
「通過審查」不等於「架構健康」
更微妙的是,AI 工具傾向於給出「看起來很專業」的評論,這會產生一種權威感的錯覺。當一個 PR 被 AI 標註了五個小問題、三個風格建議、兩個測試缺口,並且全部修正完畢後,團隊很容易產生「這個 PR 已經被徹底檢查過了」的安心感。但事實上,這個 PR 可能正在把一個核心業務邏輯塞進 UI 元件裡,或者引入了一個跨越三個 bounded context 的循環依賴——而 AI 一句話都不會說。
這不是 AI 的錯,而是我們對它的期待錯位。把 AI 當成「架構守門員」,就像請一位校對員去檢查一棟大樓的結構安全——他會抓出錯字、標點錯誤、格式不一致,但他不會告訴你樑柱的受力計算有問題。
為什麼架構問題特別難以被 AI 察覺
如果說上一節談的是「AI 看到了什麼」,這一節要談的是「架構問題為什麼天生就跟 AI 的運作方式相剋」。我將其歸納為四個核心原因:抽象層級錯位、歷史脈絡缺失、取捨判斷的不可計算性,以及缺乏執行期的系統回饋。
抽象層級的錯位:AI 在字元層運作,架構在概念層成形
大型語言模型的運作單位是 token,也就是字元或詞片的序列。它透過注意力機制在這些 token 之間建立統計關聯,進而「預測」什麼樣的程式碼接續是合理的。這套機制在處理語法、命名、常見 API 用法時非常有效,因為這些東西本身就高度依賴表層模式。
但架構不是表層模式。架構是概念與概念之間的關係:什麼是領域實體、什麼是應用服務、什麼是基礎設施、依賴方向該往哪裡指、哪些概念應該被隔離、哪些概念可以共享。這些關係存在於工程師的腦中,被部分地編碼進套件結構、介面設計與依賴注入的設定裡,但它們並沒有以一種 AI 能直接「讀懂」的形式存在。
舉個具體的例子。假設一個電商系統中,OrderService 直接 import 了 EmailTemplateRenderer。從 AI 的角度看,這是一個合法的 import,型別正確,呼叫方式也沒錯,甚至可能還有單元測試覆蓋。但從架構角度看,這代表領域層正在直接依賴簡報層的元件,這是一個明確的依賴方向違規。AI 不會知道 EmailTemplateRenderer 屬於簡報層,因為「層」的歸屬是團隊賦予的概念,不是程式碼本身的屬性——除非你把它寫進了套件命名或架構測試裡,而多數團隊並沒有做到那麼徹底。
歷史脈絡的缺失:架構是時間的產物,不是快照的產物
架構問題很少是一次性出現的。它們幾乎都是「漸進式侵蝕」的結果:今天多一個方便的直接呼叫,明天多一個「暫時」的跨層引用,後天多一個「先複製過來再說」的重複邏輯。單看任何一個 PR,這些改動都微不足道,甚至看起來很合理;但把它們疊加在三個月、半年的時間軸上,就是一個模組邊界徹底崩解的過程。
AI 程式審查工具每次看到的,都是一個時間切片。它沒有「這個模組過去被依賴的方式」的記憶,也沒有「團隊當初把這個邊界畫出來的理由」的紀錄。它不會說:「等等,這是這個月第三次有人從這個方向跨過這條邊界了,我們是不是該停下來談談?」——因為它不知道前兩次發生過。
這正是架構審查最需要的能力:趨勢感。一個好的架構師會注意到「這個套件的對外依賴數量正在穩定上升」,會警覺「這個介面的方法數已經從五個長到二十個」,會說「這條依賴路徑三個月前不存在,現在卻變成熱點」。這些都是需要累積觀察才能發現的訊號,而 AI 審查工具目前幾乎不具備這種跨 PR 的縱向記憶與趨勢分析能力。
取捨判斷的不可計算性:架構是權衡,不是對錯
程式碼審查裡有大量問題是有客觀對錯的:這個變數沒初始化、這個例外沒處理、這個迴圈會無窮執行。AI 在這些地方表現很好,因為答案是明確的。
但架構決策幾乎沒有「正確答案」,只有「在某個情境下的較佳取捨」。要不要把這個邏輯抽成獨立服務?取決於團隊規模、部署頻率、資料一致性要求、維運成本。要不要讓這兩個模組共用一個資料模型?取決於它們的演進速度是否同步、變更影響範圍是否可控。要不要引入一層抽象?取決於現在的不變性是否足夠穩定,以及團隊是否準備好承擔這層抽象的維護成本。
這些問題的答案,取決於上下文:團隊的能力、組織的結構、產品的階段、未來的預期變化。AI 可以列出某種做法的優缺點,但它無法替你做出「在這個團隊、這個時間點、這個產品階段」的判斷。而架構審查的核心工作,恰恰就是這個判斷。
更麻煩的是,許多架構問題在短期內看起來是「優化」。把兩層合併成一層、把介面拿掉直接呼叫、把共用邏輯複製一份以換取解耦的假象——這些做法在當下都能換來更少的程式碼、更快的開發速度,而代價要到半年後才會顯現。AI 審查工具沒有能力評估「半年後的維護成本」這種延遲性代價,它只會看到「程式碼變簡單了」。
缺乏執行期的系統回饋:架構的品質在執行時才真正顯現
最後一個關鍵原因是:架構的品質,很大一部分只能在執行期被觀察到。模組之間的耦合是否造成部署上的牽制、某個抽象是否導致執行路徑變長、資料流是否因為架構設計而產生不必要的轉換開銷、服務邊界是否切在錯誤的地方以至於每次業務變更都要同時改三個服務——這些問題,靜態的程式碼審查幾乎不可能捕捉。
AI 審查工具看到的是「程式碼長什麼樣子」,而架構問題往往表現在「系統跑起來是什麼樣子」。要讓 AI 具備架構感知能力,理論上需要餵給它運行時的可觀測性資料:呼叫鏈、依賴圖、變更頻率、部署耦合度、熱點分析等。但目前主流的 AI 審查工具,與 APM、分散式追蹤、依賴分析工具之間,幾乎是斷開的。它們各自看到系統的一個側面,卻沒有人把這些側面拼成一張完整的架構地圖。
架構問題的常見類型與 AI 的對應盲點
為了讓討論更具體,我們把常見的架構問題分成幾類,逐一檢視 AI 程式審查在每一類上的表現。你會發現,AI 並非完全無感,但它的反應往往是「點狀的、表層的、缺乏脈絡的」。
依賴方向違規與循環依賴
這是架構侵蝕最經典的形式。分層架構中,上層依賴下層是常態,下層依賴上層則是違規;在六角架構或整潔架構中,領域層不應該知道任何關於資料庫、框架、外部服務的細節。當這些規則被打破時,系統會逐漸變成一個「什麼都依賴什麼」的泥球。
AI 能抓到什麼?如果專案有明確的目錄結構,AI 有時能注意到「這個檔案從 domain 目錄 import 了 infrastructure 目錄的東西」,並提出警告。但這依賴於兩個前提:目錄結構本身忠實反映了架構意圖,以及 AI 被允許看到足夠多的檔案來判斷 import 路徑的語義。在多數情況下,AI 只會看到一行 import,然後判斷「這個路徑存在、這個符號存在,沒問題」。
更關鍵的是循環依賴。循環依賴很少發生在同一個 PR 裡——它是 A 依賴 B、B 依賴 C、C 又依賴 A 這樣跨越多個 PR、多個模組逐步形成的。單一 PR 的 diff 裡,AI 看到的只是一個新加的 import,它不會去追蹤整條依賴鏈,自然也看不到那個閉環正在成形。
邊界模糊與耦合失控
模組邊界模糊的徵兆包括:同一個概念在多个地方有不同實作、一個模組對外暴露的方法愈來愈多、兩個本該獨立的模組開始共享可變狀態。這些問題不會讓程式碼「壞掉」,但會讓系統愈來愈難改。
AI 在這一類問題上的表現特別無力,因為「邊界應該在哪裡」是一個設計判斷,而不是一個可以從程式碼語法推導出來的規則。AI 可能會注意到「這兩個類別長得很像」,然後建議你合併——但這可能正好是錯的,因為它們服務於不同的 bounded context,長得像只是巧合,合併反而會製造錯誤的耦合。反過來,當兩個類別應該合併卻被硬生生拆開時,AI 也看不出來,因為它不知道這兩個概念的業務語義。
抽象洩漏與不當的共用
抽象洩漏(leaky abstraction)是另一個典型的架構問題:某個抽象原本想隱藏實作細節,卻因為設計不當而讓細節滲透到呼叫端。常見的例子是一個「通用」的 repository 介面,最後被迫加上 findByComplexConditionWithJoin 這種充滿資料庫語彙的方法,讓上層被迫知道底層是關聯式資料庫。
AI 看到的是:這個介面多了一個方法,型別正確,命名符合駝峰式。它不會說「這個抽象正在洩漏」。因為「洩漏」的判斷,需要理解這個抽象原本想保護什麼,以及現在的改變破壞了什麼。這些都是意圖層面的資訊,不在程式碼的表層。
同樣地,「不當的共用」——把只是恰好長得像、但業務語義不同的東西抽成共用工具——也是 AI 的盲點。AI 傾向於鼓勵 DRY(Don't Repeat Yourself),看到重複程式碼就建議抽取。但資深工程師知道,錯誤的 DRY 比合理的重複更危險:它會把兩個本該獨立演進的概念綁在一起,未來任何一邊的變更都會牽動另一邊。AI 沒有能力區分「真正的重複」與「巧合的相似」。
技術債的隱性累積
技術債不是一個具體的程式碼問題,而是一個時間性的現象:現在的一個小方便,會在未來產生不成比例的成本。AI 審查工具的每一個判斷都基於「當下這段程式碼是否合理」,它沒有能力評估「這個決定在未來會產生什麼利息」。
例如,一個為了趕上線而硬寫進 controller 的業務邏輯,在當下看起來只是「暫時的」,AI 甚至可能覺得這段程式碼寫得還不錯——邏輯清楚、有錯誤處理、有測試。但它不會說:「這段邏輯屬於領域層,放在 controller 會讓它無法被其他入口重用,也會讓測試變成整合測試,未來每次改這個規則都要跑起整個 web 框架。」而這,正是技術債累積的典型樣貌。
實務案例與社群觀察:AI 審查在真實團隊中的落差
談完理論,我們來看看實務現場。以下幾個案例改寫自筆者觀察到的社群討論與團隊經驗,它們共同說明了一件事:AI 審查的價值真實存在,但它與架構守護之間,還有一大段距離。
案例一:被 AI 放行的跨層呼叫
某個中型 SaaS 團隊在導入 AI PR 審查工具後,發現審查速度大幅提升,PR 平均合併時間從兩天縮短到半天。團隊士氣很好,直到三個月後的一次重構,才發現問題的規模。
他們的系統原本是乾淨的分層架構:API 層、應用服務層、領域層、基礎設施層。但在这三個月裡,為了快速交付幾個功能,有工程師開始在 API 層直接注入 repository,繞過應用服務層做一些「簡單的查詢」。每一次這樣的改動,AI 審查都給了綠燈,因為從語法上看,這就是一個正常的依賴注入與方法呼叫,沒有錯誤,沒有安全問題,測試也通過了。
三個月後,應用服務層開始出現「孤島」:有些業務規則只存在於 API 層的某個 handler 裡,無法被其他入口(例如排程任務、CLI 工具)重用。團隊想把這些規則抽回去,卻發現 API 層與 repository 之間已經長出了各種隱性假設,抽離的成本遠高於當初寫進去的時候。AI 從未警告過,因為每一次單獨看,那個改動都是「合理的」。
案例二:AI 鼓勵的錯誤 DRY
另一個常見的場景:AI 在審查時發現兩個地方有相似的程式碼片段,於是建議抽取成共用函式。這個建議聽起來很合理,也經常被採納。但在某些情況下,這兩個片段其實服務於不同的業務概念,只是碰巧實作相似。
有團隊分享過一個例子:訂單折扣計算與行銷活動折扣計算,在早期的實作中恰好都是「取基本金額、乘上折扣率、四捨五入」。AI 看到重複,建議合併成 calculateDiscountedAmount。合併後,程式碼確實變短了。但半年後,行銷活動開始需要支援「階梯式折扣」與「排除特定商品」,而訂單折扣則需要支援「與會員等級連動」。兩個業務規則開始分叉,共用函式被迫長出愈來愈多的參數與條件分支,最後變成一個誰都不敢改的怪物——這正是「錯誤的 DRY」造成的架構債。
AI 不會預見這一切,因為它不知道這兩個概念未來會朝不同方向演進。它只看到當下的字元重複,而看不到未來的語義分離。
案例三:架構測試比 AI 審查更可靠的地方
也有團隊走了另一條路:他們不期待 AI 審查架構,而是把架構規則寫成可執行的測試。例如用 ArchUnit(Java)或 dependency-cruiser(JavaScript/TypeScript)這類工具,明確宣告「domain 層不得依賴 infrastructure 層」、「api 層不得直接依賴 repository」等規則,並在 CI 中強制執行。
這些架構測試沒有 AI 那麼「聰明」,它們只會檢查依賴方向、套件邊界、命名慣例這些機械性的規則。但正因為它們是機械性的、明確的、可重複執行的,它們在架構守門這件事上,反而比 AI 可靠得多。AI 可能漏看、可能被上下文誤導、可能給出聽起來很合理但實際錯誤的建議;架構測試不會。它要嘛通過,要嘛失敗,沒有模糊空間。
這個對比說明了一件事:架構規則應該被明文化、可執行化,而不是期待 AI 去「領會」。 AI 的價值在於補足人類審查的盲點與疲勞,而不是取代那些本該被自動化的明確規則。
如何結合 AI 與人類審查:把對的工具放在對的位置
那麼,實務上該怎麼做?答案不是「不要用 AI 審查」,而是「搞清楚 AI 該審什麼、人類該審什麼、以及哪些東西應該被自動化成規則」。以下是一個可操作的分工框架。
讓 AI 專注於它擅長的局部品質把關
與其期待 AI 抓架構問題,不如把它放在它真正強的位置:
測試缺口提示:AI 指出哪些分支沒有測試,對提升覆蓋率有實際幫助。
文件與註解品質:AI 能協助檢查公開 API 是否有足夠的說明。
把這些「機械性但繁瑣」的工作交給 AI,人類審查者就能把注意力集中在真正需要判斷力的地方——而那正是架構。
把明確的架構規則交給自動化工具,而不是 AI 或人腦
凡是可以用「依賴方向、套件邊界、迴圈偵測、命名空間」這類機械規則表達的架構約束,都應該寫成可執行的架構測試,放進 CI 流程。這比 AI 可靠,也比人類審查穩定——因為它不會疲勞、不會被上下文誤導、不會因為趕時間而放水。
常見的工具包括:Java 生態的 ArchUnit、.NET 生態的 NetArchTest、JavaScript/TypeScript 生態的 dependency-cruiser、Python 生態的 import-linter,以及各語言通用的依賴分析工具。這些工具能抓出 AI 最常漏掉的問題:跨層依賴、循環依賴、模組邊界違規。
更重要的是,把架構規則寫成測試的過程本身,就是一次團隊對架構意圖的對齊。很多時候,團隊成員對「這條邊界在哪裡、為什麼存在」其實沒有共識,直到開始寫架構測試,才發現彼此的認知有落差。這個對齊的價值,甚至高於測試本身。
人類審查聚焦於意圖、權衡與長期演進
人類審查者的不可替代之處,在於能處理那些「無法被規則化」的判斷:
這些問題需要的是脈絡知識與判斷力,而這兩者恰好是 AI 最缺乏的。人類審查者應該把時間從「抓 style、抓 typo」中解放出來,投入到這些真正需要人的地方。
建立「架構變更」的顯性化流程
最後,一個常被忽略的做法:讓架構變更變得顯性。當一個 PR 涉及到跨模組依賴、新增抽象、改變邊界時,它應該被清楚地標記出來,進入一個更嚴格的審查流程——也許需要架構師或資深工程師的額外確認,也許需要在 PR 描述裡說明設計決策與取捨。
這個流程的關鍵,不是增加官僚程序,而是對抗「架構侵蝕的漸進性」。因為架構侵蝕之所以難以察覺,正是因為每一次改動都顯得很小、很合理。把「架構相關變更」顯性化,就是在對抗這個漸進性——讓小改動在累積成問題之前,就有機會被看見、被討論。
結語:AI 是優秀的校對員,但不是架構師
回到本文最初的問題:AI 的程式審查,為什麼看不出架構問題?
答案可以濃縮成一句話:因為架構問題存在於概念、歷史、權衡與執行期的交會處,而 AI 審查工具被設計來處理的是字元、模式、當下的程式碼片段。 這不是 AI 不夠強,而是它的能力曲線與架構問題的性質,天生就不在同一條軸線上。
認清這個落差,不是要否定 AI 程式審查的價值。恰恰相反,正因為我們知道 AI 擅長什麼,才能把它放在真正能發揮作用的位置:樣式、常見錯誤、安全樣式、測試缺口。這些工作交給 AI,能讓人類審查者從繁瑣中解放,把注意力集中在更高價值的判斷上。
同時,我們也該把那些「可以被機械化」的架構規則,交給架構測試工具,而不是期待 AI 去「領會」或人類去「記得」。可執行的規則,永遠比善意的提醒更可靠。
至於那些真正需要判斷力的部分——這個邊界該怎麼畫、這層抽象是否值得、這個取捨在未來會產生什麼代價——它們仍然是人類的工作。而且,隨著 AI 接手更多機械性審查,這些人類專屬的工作反而會變得更清晰、更被重視。
AI 是一位出色的校對員,能幫你抓出錯字、標點、格式問題,讓你的文件更乾淨。但如果你問它「這棟大樓的結構安不安全」,它只能告訴你「每根柱子都畫得很整齊」。架構的安全,終究需要懂結構的人來看。而這,正是為什麼在 AI 時代,資深工程師的架構判斷力,不但沒有貶值,反而變得更加珍貴。
下次當你看到 AI 審查給出滿滿的綠勾勾時,記得問自己一個問題:它檢查的是這棟大樓的裝潢,還是結構? 如果答案是前者,那麼架構審查這件事,還是得由人來扛。