iT邦幫忙

2026 iThome 鐵人賽

DAY 3
0
Software Development

當 AI 寫得比你讀得快:Code Review 該審什麼系列 第 3

Day 3:為什麼「測試都綠燈」不能證明程式碼設計沒問題

  • 分享至 

  • xImage
  •  

前言:「CI 全綠,還要 review 什麼?」

這句話幾乎是每個團隊都講過的話。測試涵蓋了主要情境、CI 顯示全綠、PR 描述寫得清清楚楚——這時候要求「再花時間仔細 review 一次設計」,聽起來像是在浪費時間。

但我想先問一個問題:測試綠燈證明的是「這段程式碼做了什麼」,還是「這段程式碼該不該長這樣」? 這兩件事聽起來很像,其實是完全不同維度的問題,而 AI 生成的程式碼最擅長讓第一件事成立,同時完全不管第二件事。

今日目標

  • 分清楚「行為驗證」跟「設計驗證」是兩個不同維度,測試只回答第一個
  • ai-news-test 兩個版本同樣通過測試、卻天差地遠的實際案例說明這個落差
  • 認識「行為契約」(測試檢查的東西)跟「設計契約」(架構、複雜度、職責邊界)的差異
  • 學會在「測試通過」之後,追加至少一個關於設計合理性的問題
  • 理解為什麼這個落差在 AI 產出速度下會被放大,而不是縮小

測試在驗證什麼

一個驗收測試的結構通常是 Given-When-Then:給定某個狀態,做某個操作,得到某個結果。它驗證的是輸入跟輸出之間的對應關係——這篇文章可以發佈、那篇文章不能重複發佈。測試完全不管「這個結果是用一行邏輯算出來的,還是繞過八層抽象才算出來的」。

這不是測試設計得不夠好,這是測試這個工具本來就只負責檢查行為,不負責檔案數、呼叫鏈長度、類別職責是否清楚。用測試去檢查設計品質,就像用體溫計去量血壓——工具本身沒有問題,是拿錯工具去回答不屬於它的問題。

案例:同一組測試,兩種天差地遠的設計

ai-news-test 這個示範專案的兩個版本都通過同一組 10 個驗收測試,測試檔案一行都沒改。乾淨版本 3 個檔案、249 行;過度設計版本 745 個檔案、21,727 行。如果你只看 CI 畫面:

Tests: 10 passed

兩個版本看起來一模一樣。但如果你去改一個小需求——替文章加上「分類」欄位——代價完全不同。乾淨版本只需要動 Article.php(建構子跟 getter)、ArticleRepository.php(SQL 與資料對應)、PublishArticleService.php(方法簽名),3 個檔案。過度設計版本光是在「真正有被呼叫」的 62 個檔案範圍內,就要動到 Aggregate、TableGateway、SQL Builder、Mapper、兩組 Command/Handler/Validator、Service 本體,大約 10-11 個檔案

需求複雜度沒有變,只是多一個欄位,改動幅度卻差了 3-4 倍。這組「改動範圍」的落差,測試完全看不出來,因為兩個版本現在都通過同樣的測試——測試只在乎「文章能不能發佈」,不在乎「加一個欄位要動幾個檔案」。

具體反例:測試綠燈,行為卻沒有真正被保護

過度設計版本裡有一個很典型的例子:SqliteUnitOfWork 這個類別。

❌ 名字承諾了交易保護,實際上什麼都沒做:

class SqliteUnitOfWork implements UnitOfWorkInterface
{
    public function begin(): void    { $this->logger->log('begin (no-op)'); }
    public function commit(): void   { $this->logger->log('commit (no-op)'); }
    public function rollback(): void { $this->logger->log('rollback (no-op)'); }
}

所有測試依然全部通過——因為驗收測試的情境裡沒有一條特別去驗證「多筆寫入中途失敗時會不會回滾」。測試綠燈,不代表這裡沒有問題;測試只是沒有問到這一題。如果系統真的需要跨多筆寫入的一致性保證,這裡完全沒有提供,但沒有任何一個測試會亮紅燈告訴你。

✅ 更保守的作法:如果 review 時發現一個類別名稱承諾了某個保證(交易、重試、快取一致性),至少要反問一句「有沒有一條測試專門驗證這個保證真的存在」,而不是預設「有這個類別就代表有這個能力」。

行為契約 vs 設計契約

我們可以把「該檢查什麼」拆成兩層:

  • 行為契約:輸入輸出對不對,這是測試該負責、也真的做得到的事。
  • 設計契約:改動範圍合不合理、職責有沒有重複、抽象層次跟業務複雜度成不成比例——這是測試工具本身沒有辦法檢查的維度,需要另外的判斷或另外的規則去把關。

測試通過只證明程式碼「能動」,不證明程式碼「該長這樣」。 這句話值得重複講,因為它正是本系列反覆要處理的落差:如果 review 的標準只停在「行為契約」,AI 生成的程式碼永遠可以合法地通過審查,同時累積出後面難以維護的設計問題。

為什麼這件事在 AI 時代特別危險

人類工程師寫出「測試過但設計有問題」的程式碼,速度是有限的——一天能寫的行數就這麼多,設計問題累積的速度也慢。AI 生成程式碼的速度快了幾十倍甚至上百倍,如果 review 的判準還停在「測試綠燈就過」,設計層面的問題會用同樣的倍率累積,而人力 review 的速度沒有變快。這正是本系列的主題句:AI 沒有發明過度設計,它只是讓過度設計的速度追上了你按下 Enter 的速度;Review 要跟得上,審的就不能再是程式碼本身,而是產生程式碼的規則。 測試只是規則的其中一種,不是全部。

今日思考題

你的團隊 PR 檢查清單裡,有沒有一條是專門檢查「測試以外」的設計品質?如果拿掉 code owner 主觀的「這樣寫感覺怪怪的」判斷,你們有沒有客觀、可重複執行的方式去回答「這個改動範圍合不合理」?

今日重點回顧

  • 測試驗證的是行為契約(輸入輸出),不是設計契約(複雜度、職責、改動範圍)
  • ai-news-test 兩個版本同樣測試全過,但改一個欄位的代價差了 3-4 倍
  • SqliteUnitOfWork 案例說明:類別名稱承諾的保證,測試沒有覆蓋到就不會被抓到
  • 「測試通過」只證明「能動」,不證明「該長這樣」
  • AI 產出速度放大了行為契約跟設計契約之間的落差,review 標準必須跟著調整

明日預告

Day 4 要往回看一段技術史:過度設計不是 AI 帶來的新病,這個問題在軟體工程界已經被討論了幾十年。今天先簡短回顧這段脈絡,順便釐清 AI 到底改變了什麼、沒改變什麼。

老派工程師的心得

我自己也曾經被「CI 全綠」這四個字騙過好幾次——尤其是接手別人程式碼時,看到測試都過,會下意識放鬆戒心,直接開始改功能。後來吃過幾次虧才體會到:測試給的是「安全感」,不是「保證」,這兩者的差距,恰好就是設計品質留下的破口。 這個體會在 AI 開始大量參與寫程式碼之後變得更明顯,因為 AI 太擅長生產「能通過測試」的程式碼了,擅長到我必須提醒自己多問一句「這是真的沒問題,還是只是測試沒問到」。


上一篇
Day 2:一句模糊需求,AI 怎麼生出一份「能動但過度設計」的系統
下一篇
Day 4:過度設計不是 AI 帶來的新病——簡短技術史回顧
系列文
當 AI 寫得比你讀得快:Code Review 該審什麼7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言