跳至主要內容
技術

Agent 產出品質保證:Code Review、自動測試、與「不要太信任」的藝術

Agent 產出品質保證:Code Review、自動測試、與「不要太信任」的藝術
Agentic Engineering 實戰手冊 第 6 / 14 篇 ,前往系列總覽

這是「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 工具)在 Agentic Engineering 的 anti-patterns裡,把這件事說得很直接:

「不要提交你沒有 review 過的 code 的 pull request——無論那段 code 是你寫的還是 AI 寫的。」

這聽起來是常識,但實際操作起來很難做到,因為 agent 產出的 code 有一個特殊的心理效應:它看起來非常專業

人類寫的 code 通常有明顯的「風格」——有的人變數名取得好、有的人 error handling 做得糙、有的人 comment 寫得多。你看了幾秒就能判斷「這段 code 品質如何」。

Agent 的 code 常把表面整理得很完整:error handling、變數命名、格式,甚至 JSDoc 註解都一應俱全。這種專業外觀很容易讓大腦提前進入「品質應該不錯」的信任模式。

但 agent 會讓某些 bug 特別常見。下面不是互斥分類,而是 review 時可以提高警覺的傾向:

Review 時要找的風險人類與 Agent 都可能犯Agent 產出常見的放大效應
邏輯錯誤Off-by-one、漏邊界條件可能把同一個錯誤同步寫進實作與測試
事實錯誤記錯欄位或 API名稱很合理、語氣很確定,較容易讓人略過查證
範圍錯誤漏改或多改一次產生大量 diff,review 容易疲勞
設計錯誤過度抽象或不合現有架構會套用 training data 中常見、但不適合這個 repo 的模式

人類搞混 <=<,你盯著看就能發現。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 是你的第二道防線,而且是可重複的一道;它的可靠度取決於檢查涵蓋了什麼:

  1. Type checkingtsc --noEmit)——抓 agent 用了不存在的 type、interface 定義不匹配
  2. Unit tests——抓邏輯錯誤(但注意:如果 test 也是 agent 寫的,可能有共同盲點)
  3. Integration tests——抓 agent 對外部系統的假設錯誤(像那個不存在的 column)
  4. Lint——抓 coding convention 違規
  5. Security scanning——抓 agent 引入的潛在安全漏洞

這些「傳統工具」在 agent 時代的價值不減反增,它們免費幫你抓 agent 的基本錯誤,而且不會疲勞、不會被「看起來很專業」的 code 給騙過。

Static Analysis:比你想的更重要

TypeScript strict mode 很適合 agent-heavy 的專案。它能幫你:

  • 正確處理 nullundefined
  • 在無法推斷時避免落入 implicit any
  • 提早發現不相容的 interface 與不安全存取

以前你可能覺得 strict mode 太煩了,到處要加 type assertion。但現在它幫你攔截了 agent 一大堆「看起來對但 type 不對」的 code。

這些工具本來就是成熟工程流程的一部分。Agent 讓它們的回饋週期更有用,因為產生碼可以立刻被一致的規則檢查;但它們仍然只能證明「通過了已存在的檢查」,不是證明整個功能正確。

Human Review 的 80/20:看什麼、忽略什麼

你要對整份變更負責,但不代表每一行都投入相同注意力。小而高風險的權限設定,可能比一大段機械產生碼更值得逐行看;重點是依風險分配 review 深度,而不是依作者是人還是 AI。

重點看的四件事

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 的風險?輸入是否依資料型別與業務規則驗證?輸出到 HTML、SQL、shell 等不同位置時,有沒有做對應的 escaping 或 parameterization?有沒有暴露敏感資訊?

Agent 在安全方面的表現參差不齊。它通常知道要用 parameterized queries,但可能在比較隱蔽的地方忘記——比如動態拼接 SQL 的 ORDER BY clause。

4. 業務邏輯正確性

這個邏輯在商業意義上對嗎?不只是 code 能不能跑,是它的行為是不是你期望的?

Agent 可能精準實作了 spec 裡寫的邏輯,卻沒有察覺 spec 本身漏了規則。它可以提出疑問,但真正的業務語意仍要由了解產品的人確認。

可以快速掃的

  • Boilerplate code:imports、exports、standard config
  • Formatting:這是 linter 的工作,不是你的
  • Standard patterns:如果 agent 用了你 codebase 裡已有的 pattern,快速掃過就好

Agent PR vs 人類 PR 的 Review 重點不同

Review 重點人類 PRAgent PR
偷懶 / 走捷徑要注意也要注意
過度設計可能常見風險之一
事實性錯誤可能要主動交叉查證
不存在的 API/method可能陌生名稱一定要查
風格一致性通常沒問題可能跟專案風格不同
安全漏洞偶爾要特別注意

TDD x Agent:目前最可靠的品質保證模式

在適合自動化驗收的任務上,我很常用這個模式:先由人確認行為,再讓 agent 寫 implementation。測試可以由你寫,也可以讓 agent 起草後由你逐項 review。

工作流程:

你 → 寫 test cases(基於 spec 的 verification criteria)

Agent → 跑 test,看到 red(全部失敗)

Agent → 寫 implementation

Agent → 跑 test,看到 green(全部通過)

你 → review 測試與 implementation(確認行為、設計、安全與整合風險)

為什麼這個模式可靠?

  1. Test 是 verification criteria 的可執行版本——它能證明已列出的案例是否通過,但沒寫進測試的需求仍可能錯

  2. 你控制了「正確」的定義——你寫 test,所以你決定什麼是正確的行為。Agent 負責實現,但不負責定義。

  3. Review 更有焦點——你已有一組可重複的行為證據,但仍要檢查測試是否代表真需求,以及設計、安全、效能與整合。

  4. 降低 hallucination 的影響——如果測試真的執行到那條路徑,不存在的 API 通常會暴露;若 mock 過頭或路徑沒覆蓋,仍可能漏掉。

實際操作範例

我要加一個 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 case 沒有寫偏,再看時間邊界、時區、code 風格與不必要的 dependency。測試提供證據,不提供保證。

10 分鐘完成一個以前需要 30 分鐘的任務,而且品質更有保障,因為有 test 當安全網。

Hallucination 偵測 Patterns

Agent code 的 hallucination 有幾個常見模式,學會辨認它們可以讓你的 review 效率大幅提升:

Pattern 1:不存在的 API / Function

徵兆:Agent 呼叫了一個你沒見過的 function、method、或 API endpoint。名稱看起來很合理。

偵測:先用 rg "functionName|endpoint" 搜尋 repo,再查型別、依賴套件原始碼或官方文件。Repo 裡找不到不代表它一定不存在,也可能來自 dependency 或動態產生。

實例:Agent 寫了 response.data.items.filter(...),但你的 API 回的是 response.results,不是 response.data.items

Pattern 2:過時或不存在的 Library Method

徵兆:Agent 用了一個 library 的 method,語法看起來對,但就是跑不動。

偵測:去 library 的官方文件確認 method 是否存在。特別注意 major version 更新後被移除的 API。

實例:Agent 從舊教學抄到已棄用的 lifecycle API,或使用目前安裝版本尚未提供的方法。版本號與官方文件要一起核對。

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()。Charges API 不是單純「新版 SDK 已不存在」,但對需要處理 SCA/PSD2 與較完整付款生命週期的新整合,Stripe 建議採用 PaymentIntents。問題不在 method 能不能呼叫,而在它不符合這個產品的合規與流程需求。

在 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 秒。

如果 prompt、schema 和測試都沒提供 production 資料規模,agent 就只能從小資料集推測。這次邏輯本身沒錯,卻漏掉了沒有合適 index 的 LIKE query 在大量資料下會有多慢。

教訓:不要預設 agent 已掌握 production scale。任何涉及 database query 的 code,review 時都要問:「資料量放大 100 倍時,行為會是什麼?」

建立你的品質保證 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

### 安全性

- [ ] 輸入已按型別與業務規則驗證,輸出依使用情境正確 escaping
- [ ] 沒有 hardcoded secrets
- [ ] 沒有 SQL injection 風險
- [ ] 適當的 authorization check

### Performance

- [ ] Database query 的執行計畫與索引符合資料量及查詢模式
- [ ] 沒有 N+1 query
- [ ] 沒有不必要的 re-render / re-computation

不需要每次都過完整份 checklist。根據 agent 改動的範圍,選擇相關的項目。但「事實性檢查」那個 section,每次都要看。

Takeaway

  1. Agent 的 code 最危險的地方是「看起來很對」——格式與語氣會製造可信感,但不代表事實、行為或 production scale 正確。把每份 diff 當成待驗證的提案,而不是先假設好或壞。

  2. 自動化護欄讓回饋可重複——Pre-commit hooks、CI pipeline、TypeScript strict mode 不會因 diff 看起來專業就放水,但只會檢查你真的設定過的規則。

  3. TDD + Agent 很適合可客觀驗收的任務——先定義行為,再讓 agent 收斂到通過測試;之後仍要 review 測試盲點、設計、安全與整合風險。它是強力護欄,不是品質保證書。


上一篇:Spec-Driven Development 下一篇:從 Prompt 到 Production 的完整旅程

留言討論

esc
輸入關鍵字搜尋文章...
查看收藏 →