你只是在等紅燈而已:AI 輔助 TDD 實戰經驗
這是一份針對「你只是在等紅燈而已:AI 輔助 TDD 實戰經驗」直播內容的重點整理。講者 KUMA 透過親自進行的「不撰寫程式碼」實驗,深入探討 AI 時代下,軟體工程師如何運用測試驅動開發(TDD)與規格驅動開發(SDD)發揮自身價值,並避免被 AI 工具誤導。
一、主題由來與核心意象
- 「你只是在等紅燈而已」的由來:KUMA 在參加 JCConf 的路上,看到臺北市拆除圓環的新聞標題「你只是在等紅燈而已」,覺得這句口號非常契合 TDD 的開發循環。
- 紅綠燈的隱喻:在 TDD 中,測試失敗就如同遇到「紅燈」,開發者的目標是儘速將其轉為「綠燈」。如果紅燈與綠燈的間隔太久,開發者會產生「塞車」的挫折感;唯有採取極小步驟(Small Steps),才能縮短紅燈時間,確保程式隨時處於可交付狀態。
二、AI 時代下的「M 型化」與「傳令兵」現象
- 「0 錄取」的驚人統計:KUMA 統計了其公司 2024 年的面試紀錄,面試包含簡單的 Pair Programming 實戰(可上網、可使用 AI 工具)。在面試與筆試中,使用 AI 的 20 位候選人,最終錄取人數為 0 人。
- 中間工程師的消失(M 型化分布):AI 出現後,工程師的能力分布從常態分布走向「M 型化」。極少數優秀工程師變得更強大且昂貴,但中等能力的工程師卻大量消失,往兩端擠壓。
- 「傳令兵」工程師的致命傷:
- 許多候選人將面試官的題目直接、不加思考地轉發給 AI,再將 AI 產出的程式碼與測試直接轉發回來,自己完全成了「傳令兵」。
- 當被問到 AI 寫出的程式碼有何設計缺陷(例如是否違反開放封閉原則、有無程式碼壞味道)時,候選人竟然再次轉頭詢問 AI。
- 這種「喪失思考能力」的工程師,對企業而言,倒不如直接付費使用 AI,這也是他們不被錄取的主因。
三、AI 輔助 TDD 的核心心法:小步快跑
- 「切小」是解決大問題的有效方法:Uncle Bob 曾提到,處理大問題時,已被證實有效的方式就是將問題切小。
- 控制 AI 產出的規模:
- AI 具有非確定性(Non-deterministic),也可能產生幻覺。
- 如果一次讓 AI 產生數千行程式碼,人類審查(Review)的負擔會呈指數型上升。20 行程式可能只需看 1 分鐘,2,000 行可能要 1,000 分鐘;到 4 萬行時,人類可能放棄原則而直接盲信。
- 因此,必須將功能切小到「5~10 分鐘內能完成一輪紅綠燈循環」的程度,才能確保人類有效審查 AI 的產出。
- 撰寫測試的紀律:
- 傳統 TDD 堅持「先寫測試,再寫實作」,是為了強迫開發者從「功能需求(規格)」出發,避免被「實作細節」綁架。
- 雖然在 AI 時代,先寫哪一個的差異變小,因為 AI 能遵守「撰寫測試時不參考實作程式碼」的指示,但先寫測試(Red Light First)仍是驗證測試本身具有保護力的唯一方法。
只處理正在發生的變化
不要為猜測中的未來需求預先建立大量彈性。較合適的重構時機有兩個:
- 剛完成一個小功能、程式仍在腦中且測試全綠時。
- 下一個需求已知,並已確定現有設計不利於這個需求時。
這不是反對設計,而是要求設計決策有目前需求與測試提供的證據。長時間紅燈通常代表步驟切得太大;此時應縮小需求或測試案例,而不是繼續堆疊程式碼。
Red-Green-Refactor-Commit 實作循環
- 選擇最小案例:找出一個會迫使系統學會新行為的案例,而非機械式枚舉所有輸入。
- Red:先執行新測試,確認它因目標能力尚未存在而失敗,不是因語法、環境或測錯對象而失敗。
- Green:只加入足以通過目前案例的最小實作。硬編碼特例可以是刻意策略,下一個案例會迫使邏輯一般化。
- Refactor:在全綠的保護下改善命名、責任分配與領域模型;重構後立刻重跑測試。
- Commit:保存可回復的小步。提交邊界也是設計節奏與風險控制,不應完全交由 AI 決定。
每個新測試至少要親眼看過一次由紅轉綠,才能證明它確實能抓到錯誤。若先有實作才補測試,應透過 mutation testing、故意破壞實作或其他等效方式,確認測試真的會失敗。
四、實戰案例:完全由 AI 撰寫的德州撲克比牌器
KUMA 進行了一項嚴格的實驗:在開發德州撲克比牌器的過程中,自己不寫任何一行程式碼,所有程式碼與 Git Commit 全部交由 Claude Code 執行。
- 極細緻的測試切分:
- 最簡單的牌:起初連卡片形式、比較邏輯都沒有,只有純數字比較
2 vs 2,結果為 0,直接要求 AI 使用return 0快速通過測試、轉為綠燈。 - 加入大小比較:接著測試
2 vs 3,結果為負數,先以寫死的值通過測試,再逐步逼出真正的邏輯。 - 支援英文牌面:測試
10 vs J、K vs A,處理 A 可以是 1 或 14 的邏輯轉換。 - 加入花色與牌型:逐步從高牌(high card)、一對(one pair)測試到同花順。
- 支援多人比牌:寫完 3 人比牌邏輯後,就無須再寫 4 人比牌,因為真正的多人比牌邏輯已被測試逼出。
- 最簡單的牌:起初連卡片形式、比較邏輯都沒有,只有純數字比較
- 效益:在完整的測試保護傘下,AI 無論如何重構或修改,都不必擔心破壞功能。
五、重構與程式碼壞味道的 AI 實踐
KUMA 強調,「功能寫對了,不代表就是好程式」。即使是 AI 產出的程式碼,也必須主動辨識程式碼壞味道(Code Smells),並引導 AI 重構:
- 基本型別偏執(Primitive Obsession):AI 一開始使用簡單的字串或數字表達撲克牌點數。KUMA 指出此壞味道後,引導 AI 重構成列舉(Enum),使程式碼更貼合真實世界的概念。
- 依戀情結(Feature Envy):
PokerComparator的比較方法過度呼叫Card物件的 getter。KUMA 要求 AI 檢視 Feature Envy,並將比較邏輯收攏回Card物件本身。 - 散彈式修改(Shotgun Surgery):處理牌型(
HandType)時,新增順子等牌型必須同時修改 Enum 定義與內部判定邏輯。KUMA 指出這裡有輕微的散彈式修改後,AI 利用 Java 的函數式介面(Functional Interface)Checker重構並收攏牌型判定邏輯,讓新增牌型只需修改單一位置。 - 反覆要求可讀性:當 AI 寫出複雜難懂的演算法,例如德州撲克七選五的組合,即使測試通過,KUMA 仍連續 8 次要求 AI「我看不懂,換一種寫法」,直到產出符合人類閱讀直覺的程式碼。
測試通過只證明已描述的行為成立,不保證架構合理、領域模型精準、責任配置清楚或程式容易修改。測試提供重構安全網,但「看得懂、能維護」仍是人的驗收條件。
六、SDD 的本質與規格撰寫技巧
- 規格驅動開發並非新概念:
- 2004 年,約克大學的 Ostroff 教授便發表 Agile SDD 論文,提倡結合 TDD 與合約式設計(Design by Contract)。
- 無論是 TDD、BDD 或 SDD,核心都在於建立一個**「違反規定就會報錯」的強力模具**。
- 如何撰寫好的規格(User Story):
- 移除不可量化的形容詞:例如「快速方便的管理」、「簡單好操作」等字眼並非功能需求,無法精確定義。
- 保持 Precise(精確)與 Concise(精簡):專注描述系統要做什麼,並向 AI 說明「為什麼要做」,也就是商業目的。
- 拆分需求,降低 AI 的認知負擔:將「新增活動」與「排序/篩選活動」拆成兩張獨立卡片分段交付,避免 AI 同時考慮過多問題而產生幻覺。
- 分離規格中的抽象層級:
- 不應將過於細節的介面操作,例如點選哪兩個按鈕、等待載入,寫在功能描述(Spec/Feature 檔案)中。
- 高階規格(Spec)應專注於系統行為與功能,而底層的網頁操作細節則封裝在黏合程式碼(Glue Code)內,例如 Playwright 的操作封裝,以重複利用規格並降低認知負擔。
好規格的五項特徵
- Precise:詞義精準,避免「快速、方便、簡單好用」等無法驗證的形容詞。
- Concise:只保留當前功能需要的資訊。
- Single-purpose:一張需求只處理一個可獨立驗收的行為。
- Observable:結果能由測試或明確契約觀察。
- Consistent:領域名詞跨文件、圖表與程式碼一致。
Spec 與測試像模具,將非確定性的 AI 產出限制在可控範圍。測試與契約是可執行規格,違反時會明確失敗;只有描述、無法驗證的文件,限制力通常較弱。
行為優先,不鏡像程式結構
測試應保護使用者可觀察的行為,而不是為每個 service、repository 或類別建立與實作一一對應的測試。以購物流程為例,應驗證「完成購買」,而非鎖死內部方法的呼叫順序。
TDD、BDD、Cucumber 或一般單元測試都不是目的。只要測試能提供足夠保護、快速回饋,而且案例切得夠小,就是有價值的開發回饋機制。
七、AI 與人的責任邊界
適合交給 AI 的工作
- 依明確的輸入與預期輸出產生第一版測試。
- 執行測試並回報失敗。
- 撰寫最小實作、修正錯誤並重跑測試。
- 依已辨識的壞味道或設計方向提出重構版本。
- 產生提交訊息、樣板與重複性程式碼。
- 根據明確範例套用團隊既有架構與程式風格。
可以讓 AI 處理「自己做得出來,但不值得親手重複做」的工作;若產出完全看不懂,就無法有效審查,也無法承擔結果。
人不可外包的責任
- 把大問題切成下一個可驗證的小行為。
- 判斷測試是否真的測到需求,而非實作細節。
- 確認紅燈原因正確、綠燈不是假通過。
- 辨識程式碼壞味道與架構偏差。
- 決定領域語言、模組邊界、責任分配與非功能需求。
- 審查 AI 產出,判斷是否可理解、可維護、安全且符合團隊規範。
控制每輪輸出規模
一次只讓 AI 處理:
- 一個案例。
- 一個失敗原因。
- 一小段實作。
- 一種重構意圖。
- 一次測試與審查。
八、常見陷阱
- 把自己變成 AI 的傳令兵:需求、測試與審查都原封不動轉交 AI,人沒有提供專業判斷,也就無法建立可信任的品質門檻。
- 測試一開始就是綠燈:案例可能沒有引入新行為,或只是跟著既有實作寫;應確認目標行為缺失時它確實會失敗。
- 一次要求太多功能:多個獨立行為混在同一張需求,會增加走偏、狀態混淆與整批重做的機率。
- 用測試通過取代設計審查:功能正確不代表程式品質良好。
- 過早設計未來彈性:為尚未發生的需求建立抽象,會讓現在的步驟變大、紅燈變久。
- 規格名詞不一致:同一個概念使用不同名稱,會同時增加人與 AI 的理解成本。
九、落地檢查表
開始前
- 能用一句話描述這一輪唯一要新增的行為。
- 案例足夠小,預期能在數分鐘內完成紅綠循環。
- 輸入、動作、輸出與邊界條件明確。
- Spec 沒有混入不可驗證的形容詞。
- 領域名詞與現有文件一致。
Red
- 已實際執行測試,且失敗原因正是缺少目標行為。
- 已閱讀 AI 產生的測試,沒有把實作細節當成需求。
- 測試先於目標能力存在,或已用等效方式證明它會失敗。
Green
- 只加入足以通過目前案例的最小變更。
- 已實際重跑測試並看到綠燈。
- 沒有順手加入下一個需求。
- AI 的每一項變更都能由人解釋。
Refactor
- 重構前所有測試保持綠燈。
- 本輪只處理一個清楚的設計問題。
- 重構沒有改變外部行為,完成後已再次執行測試。
- 結果比原本更容易理解與修改,而不只是更抽象。
提交與下一輪
- 提交內容小而單一,訊息能描述行為或設計意圖。
- 沒有把大量未審查的 AI 產出一起提交。
- 下一個案例帶來新的行為轉折,而非重複已證明的規則。
- 若步驟開始變慢,先切小再繼續。
十、結論:AI 是能力放大器
- 基本功與知識比以前更重要:
- 在輸入程式碼(Type Code)這件事上,人類難以勝過 AI。
- 然而,AI 產出程式碼的品質,取決於人類的知識程度。如果不懂軟體設計、乾淨架構(Clean Architecture)、物件導向(OO)或函數式程式設計(FP)的原則,就無法辨識 AI 程式碼的壞味道,更無法提出正確的重構方向。
- 小時不讀書,長大被 AI 騙:KUMA 警示,如果工程師缺乏基本素養,AI 產出回傳
200 OK卻內含錯誤碼的不良設計,或寫出具有嚴重資安漏洞的程式碼,工程師可能全盤接收,迅速做出品質不佳的系統。 - 工程師的核心責任未曾減少:AI 只是將開發起點往前推,人類不再需要把大量時間花在輸入基礎程式碼上。省下的時間應用來精進軟體架構、設計模式、領域模型(Domain Model)與溝通管理能力,主導並把關產品品質。