這句話幾乎是每個團隊在 code review 時都會遇到的攔阻論點:PR 附上了測試報告,10 個測試全部通過,提出設計疑慮的人反而顯得像是在「找麻煩」。如果測試是規格,測試又都通過了,不就代表這段程式碼「對」嗎?
Day 1 已經先立了這個系列的一個核心判斷:測試驗證的是「行為」,不是「設計」。 今天要用版本 A 跟版本 B 的實際對照,把這句話講得更具體——兩者的測試結果完全相同,但只有一個版本的「設計」是對的,而這個差異,測試報告本身完全看不出來。
版本 A 跟版本 B 執行的是完全相同、一字未改的 tests/Feature/PublishArticleTest.php,兩者都是:
Tests: 10 passed
如果 code review 的唯一依據是「CI 有沒有綠燈」,這兩個版本會得到一模一樣的通過標記。但版本 A 是 3 個檔案、249 行,版本 B 是 745 個檔案、21,727 行,其中 92% 從未被這 10 個測試觸碰過。測試報告完全沒有能力區分這兩者——它只能告訴你「輸入 X 會得到輸出 Y」,不會告訴你這個輸出是用一行程式碼算出來的,還是繞過四層裝飾器、CQRS Bus、事件廣播才算出來的。
「行為契約」指的是:給定某個輸入或操作,系統應該產生什麼樣可觀察的結果——這正是驗收測試在驗證的東西,也是版本 A、版本 B 都滿足的部分。
「設計契約」指的是一組沒有寫進驗收測試、卻同樣重要的期待,例如:改動的範圍應該跟需求的範圍成比例、一段程式碼的存在應該對應一個真正被使用的路徑、一個類別的名字應該忠實反映它的行為。這些期待沒有辦法被單一一次的「輸入輸出」測試直接驗證,但它們決定了這個系統半年後還能不能被安全地修改。
版本 B 在「行為契約」上完全合格——它確實讓 10 個測試通過。但在「設計契約」上全面失守:SqliteUnitOfWork 的方法名字承諾交易保護,實際上是空的;RetryingArticleRepositoryDecorator 承諾重試,實際上永遠只跑一次;ArticleDTO/ArticleAggregateToDTOMapper 存在,卻沒有任何呼叫路徑用得到它們;新增一個「分類」欄位,乾淨版本改 3 個檔案,過度設計版本要改 10-11 個檔案。
❌ 只看行為契約的 review 心態:
測試綠燈 → 這個 PR 沒問題 → 合併
✅ 同時檢查設計契約的 review 心態:
測試綠燈 → 這個改動的範圍跟需求的範圍成不成比例?
→ 新增的類別/方法,有沒有真的被呼叫到?
→ 這個名字承諾的行為,程式碼裡有沒有真的做到?
如果一套 review 流程,面對規模差 87 倍的兩份程式碼,給出完全相同的「通過」結論,這代表這套流程本身的鑑別力已經失效了——它只能分辨「這段程式碼有沒有滿足行為規格」,分辨不出「這段程式碼是不是用一個合理的方式滿足規格」。而 AI 產出程式碼最擅長的,正是精準命中前者、對後者毫無自覺。這也是為什麼這個系列從 Day 1 開始就強調:AI 沒有發明過度設計,它只是讓過度設計的速度追上了你按下 Enter 的速度;Review 要跟得上,審的就不能再是程式碼本身,而是產生程式碼的規則。「規則」指的正是能夠捕捉設計契約的檢查——複雜度預算、改動幅度比例、死碼偵測——而不是再多讀幾遍程式碼本身。
如果你的團隊現在的 code review checklist 只有「測試有沒有過」「有沒有明顯 bug」,你覺得要加上哪一條,才能開始捕捉「行為對但設計錯」的情況?
10 passed,測試報告本身無法區分兩者的設計品質Day 15 會誠實地討論「逐行 review 20000 行程式碼要花多久」這個問題——不會給你一個編造的精確數字,而是講清楚為什麼 92% 死碼這種規模的問題,光憑肉眼幾乎不可能在短時間內確認。
我自己過去很長一段時間,也把「測試綠燈」當成 review 時可以稍微鬆懈的訊號——反正邏輯有測試把關,重點放在看程式碼風格、變數命名就好。這次做這個對照實驗,才真正被逼著承認:我把「行為對」跟「這個實作方式合理」這兩件事混為一談太久了。 測試綠燈從來就只回答了前者,後者需要另外一套完全不同的檢查方式,而這套檢查方式,恰好是這個系列後半段要講的東西。