Agent 產出品質保證:Code Review、自動測試、與「不要太信任」的藝術
這是「Agentic Engineering 實戰手冊」系列的第六篇。上一篇:Spec-Driven Development
那個不存在的 Database Column
我曾經讓 agent 寫了一個看起來完美的 API endpoint。Code 結構清晰、error handling 完整、連 Swagger 文件都自動加了。PR review 時同事看了也說「不錯」。
上線之後,第一個 request 就 500 了。
原因是 agent 在 SQL query 裡引用了一個叫 user_preferences 的 column,而這個 column 在我們的 database schema 裡根本不存在。它大概是從 training data 裡某個類似的 schema 推斷出來的。
最可怕的不是 bug 本身,而是它看起來太像真的了。Column 名稱合理、query 語法正確、連 JOIN 的邏輯都對,除了那個 column 是虛構的。如果不是上線後第一個 request 就爆,可能在 staging 跑幾天都沒人發現,因為 staging 的數據量小,那個 query path 可能根本沒被觸發。
這件事教了我一個血淋淋的教訓:Agent 的 code 最危險的地方不是它寫得差,是它寫得太好了,好到讓你降低了戒心。
Trust But Verify:Agent Code 為什麼特別需要 Review
Simon Willison(Django 共同創辦人,AI 工具的重度使用者和評論者)說過一句讓我印象深刻的話:
「不要提交你沒有 review 過的 code 的 pull request——無論那段 code 是你寫的還是 AI 寫的。」
這聽起來是常識,但實際操作起來很難做到,因為 agent 產出的 code 有一個特殊的心理效應:它看起來非常專業。
人類寫的 code 通常有明顯的「風格」——有的人變數名取得好、有的人 error handling 做得糙、有的人 comment 寫得多。你看了幾秒就能判斷「這段 code 品質如何」。
Agent 的 code 不一樣。它永遠有完整的 error handling、有意義的變數名、一致的格式、甚至還有 JSDoc 註解。它看起來像資深工程師寫的 code。這讓你的大腦自動進入「這個人的 code 品質不錯」的信任模式。
但 agent 的 bug 跟人類的 bug 根本不是同一種。
| 人類的 bug | Agent 的 bug | |
|---|---|---|
| 出錯層級 | 邏輯層 | 事實層 |
| 典型例子 | Off-by-one、邊界條件沒處理 | 引用不存在的 API、fabricated column |
| 容易發現嗎 | 比較容易(邏輯不對會「看起來怪」) | 很難(看起來完全正確) |
| 傳統測試能抓到嗎 | 通常可以 | 不一定(如果測試本身也是 agent 寫的) |
人類搞混 <= 和 <,你盯著看就能發現。Agent 自信滿滿地用了一個不存在的 API endpoint,你光看 code 根本看不出來,因為 endpoint URL 的格式完全正確,只是那個路徑在你的系統裡不存在。
這就是為什麼「trust but verify」在 agent 時代不只是好習慣,而是必要的紀律。
自動化護欄:讓機器先幫你擋一層
好消息是,很多 agent 的基本錯誤可以被自動化工具攔截。而且這些工具你本來就應該有。
Pre-commit Hooks
在 agent 時代,pre-commit hooks 的 ROI 翻倍了。以前它們主要是「提醒人類注意格式」,現在它們是「攔截 agent 的基本錯誤」:
# 我的 pre-commit hook 幫我擋過的 agent 錯誤
- TypeScript 類型錯誤(agent 用了不存在的 type)
- ESLint 違規(agent 用了 var 而不是 const)
- Import 循環(agent 不知道我們的 module boundary)
- 意外的 console.log(agent 習慣性加 debug log)
重點:永遠不要讓 agent 用 --no-verify bypass hooks。這是你的第一道防線。
CI Pipeline
CI 是你的第二道防線,也是最可靠的那一道:
- Type checking(
tsc --noEmit)——抓 agent 用了不存在的 type、interface 定義不匹配 - Unit tests——抓邏輯錯誤(但注意:如果 test 也是 agent 寫的,可能有共同盲點)
- Integration tests——抓 agent 對外部系統的假設錯誤(像那個不存在的 column)
- Lint——抓 coding convention 違規
- Security scanning——抓 agent 引入的潛在安全漏洞
這些「傳統工具」在 agent 時代的價值不減反增,它們免費幫你抓 agent 的基本錯誤,而且不會疲勞、不會被「看起來很專業」的 code 給騙過。
Static Analysis:比你想的更重要
TypeScript strict mode 是 agent coding 的最佳拍檔。它強制 agent:
- 正確處理
null和undefined - 明確標註所有的 type
- 不能用 implicit any
以前你可能覺得 strict mode 太煩了,到處要加 type assertion。但現在它幫你攔截了 agent 一大堆「看起來對但 type 不對」的 code。
ROI 翻倍定律:在傳統開發裡,這些工具是 nice-to-have。在 agent 時代,它們是 prerequisites。如果你的專案還沒有 CI、沒有 type checking、沒有 linting——現在就是設定它們的最好時機。
Human Review 的 80/20:看什麼、忽略什麼
你不需要(也不應該)逐行 review agent 的每一行 code。那太慢了,而且很多 boilerplate code review 起來沒有意義。
重點看的四件事
1. 架構決策
Agent 選了什麼設計模式?建了新的 module 還是擴展現有的?引入新的 abstraction 了嗎?
這些是 agent 最容易做出「合理但不適合」的決策的地方。它可能在你不需要的時候引入了 factory pattern、建了一個不必要的 service layer、或把應該是 utility function 的東西變成了一個 class。
2. 邊界條件
空值怎麼處理?array 為空時呢?API timeout 呢?concurrent access 呢?
Agent 通常會處理「happy path」很好,但在邊界條件上可能做出不合理的假設——比如假設某個 API 一定會回資料、假設某個 array 一定有元素。
3. 安全性
有沒有 SQL injection 的風險?用戶輸入有沒有 sanitize?有沒有暴露敏感資訊?
Agent 在安全方面的表現參差不齊。它通常知道要用 parameterized queries,但可能在比較隱蔽的地方忘記——比如動態拼接 SQL 的 ORDER BY clause。
4. 業務邏輯正確性
這個邏輯在商業意義上對嗎?不只是 code 能不能跑,是它的行為是不是你期望的?
Agent 可能完美地實作了你 spec 裡寫的邏輯——但如果你的 spec 有遺漏,agent 不會幫你補。它不懂你的業務。
可以快速掃的
- Boilerplate code:imports、exports、standard config
- Formatting:這是 linter 的工作,不是你的
- Standard patterns:如果 agent 用了你 codebase 裡已有的 pattern,快速掃過就好
Agent PR vs 人類 PR 的 Review 重點不同
| Review 重點 | 人類 PR | Agent PR |
|---|---|---|
| 偷懶 / 走捷徑 | 要注意 | 不太需要 |
| 過度設計 | 偶爾 | 經常 |
| 事實性錯誤 | 少見 | 常見 |
| 不存在的 API/method | 幾乎不會 | 一定要查 |
| 風格一致性 | 通常沒問題 | 可能跟專案風格不同 |
| 安全漏洞 | 偶爾 | 要特別注意 |
TDD x Agent:目前最可靠的品質保證模式
經過一年的實驗,我找到了一個可靠度最高的工作模式:你寫 test,agent 寫 implementation。
工作流程:
你 → 寫 test cases(基於 spec 的 verification criteria)
↓
Agent → 跑 test,看到 red(全部失敗)
↓
Agent → 寫 implementation
↓
Agent → 跑 test,看到 green(全部通過)
↓
你 → review implementation(已知行為正確,focus 在設計和安全)
為什麼這個模式可靠?
-
Test 是你的 verification criteria 的程式碼版本——agent 不可能「看起來對但其實錯」,因為 test 會直接告訴它對不對
-
你控制了「正確」的定義——你寫 test,所以你決定什麼是正確的行為。Agent 負責實現,但不負責定義。
-
Review 變得更輕鬆——你已經知道功能行為是正確的(test 通過了),review 只需要關注設計品質和安全性。
-
降低 hallucination 的影響——即使 agent 用了不存在的 API,test 跑下去一定會失敗,agent 就被迫換一個真正能用的方法。
實際操作範例
我要加一個 formatRelativeDate function:
Step 1:我寫 test
describe('formatRelativeDate', () => {
it('returns "just now" for dates within 1 minute', () => {
const now = new Date();
expect(formatRelativeDate(now)).toBe('剛剛');
});
it('returns "N minutes ago" for dates within 1 hour', () => {
const thirtyMinAgo = new Date(Date.now() - 30 * 60 * 1000);
expect(formatRelativeDate(thirtyMinAgo)).toBe('30 分鐘前');
});
it('returns "N hours ago" for dates within 24 hours', () => {
const fiveHoursAgo = new Date(Date.now() - 5 * 60 * 60 * 1000);
expect(formatRelativeDate(fiveHoursAgo)).toBe('5 小時前');
});
it('returns formatted date for dates older than 24 hours', () => {
const oldDate = new Date('2026-01-15');
expect(formatRelativeDate(oldDate)).toBe('2026/01/15');
});
});
Step 2:告訴 agent「讓這些 test 通過」
Step 3:Agent 寫出 implementation,跑 test,全部 green。
Step 4:我 review——不需要逐行看邏輯對不對(test 保證了),只需要看 code 風格是否一致、有沒有不必要的 dependency。
10 分鐘完成一個以前需要 30 分鐘的任務,而且品質更有保障,因為有 test 當安全網。
Hallucination 偵測 Patterns
Agent code 的 hallucination 有幾個常見模式,學會辨認它們可以讓你的 review 效率大幅提升:
Pattern 1:不存在的 API / Function
徵兆:Agent 呼叫了一個你沒見過的 function、method、或 API endpoint。名稱看起來很合理。
偵測:grep -r "functionName" . 或 grep -r "endpoint" .。如果在 codebase 裡找不到定義,很可能是 hallucination。
實例:Agent 寫了 response.data.items.filter(...),但你的 API 回的是 response.results,不是 response.data.items。
Pattern 2:過時或不存在的 Library Method
徵兆:Agent 用了一個 library 的 method,語法看起來對,但就是跑不動。
偵測:去 library 的官方文件確認 method 是否存在。特別注意 major version 更新後被移除的 API。
實例:Agent 用了 React 17 的 componentWillMount,但你的專案是 React 18。
Pattern 3:Comment 跟 Code 不一致
徵兆:Agent 寫的 comment 描述了一個行為,但 code 做的是另一件事。
偵測:比較 comment 跟 code 的邏輯。Agent 有時候會先「想好」它要做什麼(寫 comment),然後在實作的時候偏離了。
實例:
// 排除已過期的 items
const filtered = items.filter((item) => item.expiresAt > Date.now());
// ^^^ 看起來對,但如果 expiresAt 是 ISO string 不是 timestamp,
// 這個比較永遠是 true
Pattern 4:過度自信的完美 Code
徵兆:Agent 的 code 裡沒有任何 TODO、沒有任何 edge case 處理、沒有任何不確定的部分。一切都很「完美」。
偵測:這反而是一個警訊。真實世界的 code 總是有 trade-off 和待處理的 edge case。如果 agent 的 code 看起來「太完美」,很可能是它忽略了一些它應該考慮但沒考慮的東西。
三個我沒 Review 就 Merge 的慘案
慘案 1:虛構的 Database Column
就是開頭說的那個。user_preferences column 根本不存在。
教訓:Agent 寫的任何 database query,都要交叉比對 schema。特別是那些「看起來很合理但你不記得有沒有」的 column。
慘案 2:用了已 Deprecated 的 API
Agent 寫了一個 Stripe 付款整合,用了 stripe.charges.create()。這個 API 在 Stripe 的舊版 SDK 裡有效,但我們用的是新版 SDK,應該用 stripe.paymentIntents.create()。
在 staging 測試時居然通過了——因為 Stripe 為了向後相容,舊 API 在測試模式下還能用。上了 production 才發現部分付款行為不符合 PSD2 法規要求。
教訓:Agent 的 training data 會有時間差。它對 library API 的認知可能落後一兩個 major version。碰到第三方 API 整合,一定要查官方文件確認當前推薦的做法。
慘案 3:沒有 Index 的 SQL Query
Agent 寫了一個 search query,用了 LIKE '%keyword%' 做全文搜尋。在 staging 的 1000 筆資料跑得飛快。上了 production 的 500,000 筆資料,平均回應時間 12 秒。
Agent 不知道 production 有多少資料,也不知道沒有 index 的 LIKE query 在大數據量下的效能。它在小數據集上寫出了正確的邏輯,但完全沒考慮 scale。
教訓:Agent 不會幫你想 production scale。任何涉及 database query 的 code,review 時要問自己:「這在 100x 數據量的時候,行為會是什麼?」
建立你的品質保證 Checklist
根據一年的經驗,我整理了一份 agent code review 的 checklist。每次 review agent 的 PR 之前過一遍:
## Agent Code Review Checklist
### 自動化(CI 應該已經幫你做了)
- [ ] TypeScript / type check 通過
- [ ] Lint 通過
- [ ] 現有 test 全部 pass
- [ ] Build 成功
### 事實性檢查(agent 特有)
- [ ] 引用的 API / method 都真的存在
- [ ] Import 的 module 都真的存在
- [ ] Database schema 跟 query 一致
- [ ] 第三方 library 的 API 是當前版本的
### 設計層面
- [ ] 有沒有不必要的新 dependency
- [ ] 有沒有過度設計(你只要 A,它做了 A+B+C)
- [ ] 架構選擇是否適合這個 codebase
### 安全性
- [ ] 用戶輸入有 sanitize
- [ ] 沒有 hardcoded secrets
- [ ] 沒有 SQL injection 風險
- [ ] 適當的 authorization check
### Performance
- [ ] Database query 有 index
- [ ] 沒有 N+1 query
- [ ] 沒有不必要的 re-render / re-computation
不需要每次都過完整份 checklist。根據 agent 改動的範圍,選擇相關的項目。但「事實性檢查」那個 section,每次都要看。
Takeaway
-
Agent 的 code 最危險的地方是「看起來很對」——它寫出來的 code 格式完美、結構清晰、命名專業,但可能引用了不存在的 API、使用了過時的語法、或忽略了 production scale。永遠假設有 bug,直到自動化工具和你的 review 證明它是對的。
-
自動化護欄在 agent 時代 ROI 翻倍——Pre-commit hooks、CI pipeline、TypeScript strict mode,這些「傳統工具」現在是你的第一道防線。它們不會被「看起來很專業」的 code 給騙過。
-
TDD + Agent 是目前最可靠的品質保證模式——你寫 test 定義「什麼是對的」,agent 寫 implementation 讓 test 通過。Review 的壓力從「全部都要看」變成「只看設計和安全」,效率和品質同時提升。