跳至主要內容
技術

Spec-Driven Development:寫給 Agent 的需求文件,比寫給人的還嚴格

Spec-Driven Development:寫給 Agent 的需求文件,比寫給人的還嚴格
Agentic Engineering 實戰手冊 第 5 / 14 篇

這是「Agentic Engineering 實戰手冊」系列的第五篇。上一篇:Context Engineering 深度解析

同一個功能,兩份 Spec,天壤之別的結果

同一個功能需求,我寫了兩份 spec 給 agent。一份花了 30 秒隨手打:「幫我加一個用戶通知功能。」另一份花了 10 分鐘認真寫。

30 秒那份,agent 寫了 400 行 code——email notification、push notification、in-app notification 全做了,還自己加了一個 notification preference 頁面。很「完整」,但我只需要一個簡單的 in-app toast。多花了 3 小時拆掉不需要的東西。

10 分鐘那份,agent 精準地做了一個 toast component + API endpoint + 測試。一次通過。

你花在寫 spec 的時間,省下的是 agent 亂做的時間。這個 ROI 大概是 1:20。

這不是偶發事件。在過去一年裡,我追蹤了自己的任務完成情況,發現一個殘酷的規律:

Spec 投入時間Agent 產出品質人工修正時間總時間
<1 分鐘方向錯誤、過度設計2-4 小時~3 小時
5-10 分鐘基本正確、細節需調15-30 分鐘~30 分鐘
15-20 分鐘精準、一次通過<10 分鐘~25 分鐘
>30 分鐘不一定更好邊際效益遞減

甜蜜點在 10-15 分鐘。超過 20 分鐘的 spec,邊際效益開始遞減——你可能寫了太多 agent 不需要的資訊,反而 稀釋了重點

為什麼「口頭說一下」在 Agent 時代行不通

在傳統團隊裡,你可以跟同事說「幫我加一個通知功能」,然後他會:

  • 先看現有的 codebase 有沒有類似的東西
  • 不確定的地方來問你「你是要 email 還是 in-app?」
  • 看了 mockup 之後說「這個 toast 的位置應該在右上角嗎?」
  • 做的過程中發現問題會暫停,來找你討論

Agent 不會做任何這些事。它拿到「加一個通知功能」之後,就會以最大解釋範圍去實作。它不會說「你確定要這些嗎?」它只會說「好的,我幫你做了 email + push + in-app + preference page + unsubscribe + 多語系支援」。

Agent 的預設行為是「盡可能做多」,不是「釐清需求」。

這不是 agent 的缺陷。這是 LLM 的本質——它被訓練成「有幫助的」(helpful),而「有幫助」在 training data 裡通常意味著「做完整一點」。你要反過來利用這個特性:不是教 agent 少做一點,而是在 spec 裡明確告訴它「做什麼」和「不做什麼」。

我之前分享過的 失敗案例 裡有一個故事——agent 寫了 800 行白寫的 code,根源就是 spec 問題。那次我學到:模糊的需求 + 認真的 agent = 一堆你不需要的功能。

Spec 三要素:Goal / Constraints / Verification

好的 agent spec 只需要三個部分。不多不少。

1. Goal — 你到底要什麼

不是「怎麼做」,是「最後要什麼結果」。

壞的 Goal

用 React 的 useState 和 useEffect 寫一個 toast notification component,
用 CSS transition 做動畫...

這不是 Goal,這是 implementation plan。你把 agent 的手綁住了——它可能知道更好的做法,但你已經指定了每一步怎麼走。

好的 Goal

在頁面右上角顯示一個 toast notification。
3 秒後自動消失。支援 success / error / info 三種類型。
可以從任何 component 觸發。

告訴 agent 你要的結果,讓它選擇做法。它可能用 portal、可能用 store、可能用 custom event——哪種做法最適合你的 codebase,它比你更清楚(前提是它有足夠的 context)。

2. Constraints — 不要做什麼

這是 spec 裡最容易被忽略、但最重要的部分。

為什麼重要:Agent 的傾向是「做多」。不告訴它不要做什麼,它就會做所有它認為「有幫助」的事情。

Constraint 範例

## Constraints
- 只做 in-app toast,不做 email 或 push notification
- 不需要 persistence(頁面 reload 後消失就好)
- 不要新增任何 npm 依賴——用現有的 utility
- 不修改任何現有 component 的 interface
- 整個功能控制在 100 行以內

每一條 constraint 都可能省你 1 小時的拆除工作。尤其是「不要新增 npm 依賴」這種——agent 最喜歡引入新的 library 來解決問題,但你可能不希望為了一個 toast 多一個 dependency。

3. Verification Criteria — 怎麼判斷做對了

這是你跟 agent 之間的「合約」。做到這些就算完成,沒做到就需要修正。

壞的 Verification

Toast 要能用。

好的 Verification

## Verification Criteria
1. 呼叫 showToast({ type: 'success', message: 'Saved!' }) 後,
   右上角出現綠色 toast,3 秒後消失
2. 呼叫 showToast({ type: 'error', message: 'Failed' }) 後,
   右上角出現紅色 toast,3 秒後消失
3. 連續呼叫 3 次,3 個 toast 垂直排列,各自計時消失
4. npm run build 通過,沒有 TypeScript 錯誤
5. 新增至少 3 個 unit test 覆蓋以上場景

越具體越好。Agent 可以用這些 criteria 來自我驗證——它不需要你手動檢查,它可以自己跑測試來確認是否達標。

如果你搭配 TDD,verification criteria 可以直接變成 test cases,讓自動化幫你驗收。

Task Decomposition:大任務怎麼拆

一個 feature 通常不應該是一個 agent task。它應該被拆成 3-5 個 agent-executable 的單元。

拆的粒度:sweet spot

太粗剛好太細
「做一個 blog 系統」「加一個 related posts component」「在第 42 行加一個 import」
Agent 自己做太多決策Agent 有明確範圍Overhead > 效益
產出難以 review產出 = 一個 reviewable PR你不如自己做

經驗法則:一個好的 agent task 的大小,大約等於一個 reviewable PR——改動 3-10 個檔案、100-500 行 code、有明確的功能邊界。

拆法範例

Feature:在部落格加搜尋功能。

Bad decomposition(太粗):

  1. 加搜尋功能

Good decomposition

  1. 建立搜尋 index:在 build time 從所有 blog posts 產生 JSON search index
  2. 搜尋 UI component:建立 SearchBar + SearchResults component
  3. 搜尋邏輯:實作 fuzzy search,支援標題 + 內容 + tags
  4. 鍵盤導航:Cmd+K 開啟搜尋、方向鍵選擇結果、Enter 導航
  5. 整合測試:驗證 search index 產生、搜尋結果正確、keyboard navigation

每個 task 都有明確的輸入和輸出。Agent 可以一個一個做,每做完一個你 review 一次。如果第 3 步的搜尋邏輯出了問題,你只需要修那一步,不影響其他的。

AWS Kiro 的 Spec-First 理念

AWS 在 2025 年推出的 Kiro IDE 把 spec-driven 的理念直接建進了工具裡。在 Kiro 裡:

  1. 你先寫一份結構化的 spec(它叫 “Spec Document”)
  2. Kiro 自動把 spec 拆成 subtasks
  3. 每個 subtask 自動產生 acceptance criteria
  4. Agent 按照 subtask 順序執行,每步完成後自動跑 acceptance tests

雖然我主要用 Claude Code 不用 Kiro,但它的核心理念值得學習:把 spec 當成第一公民,而不是附帶產物

對照實驗:Bad Spec vs Good Spec

讓我用一個更技術的例子——「在 API 上加 rate limiting」。

Bad Spec

幫我在 API 上加 rate limiting。

Agent 的產出(摘要):

  • 引入了 express-rate-limit npm package
  • 在所有 API endpoints 上加了 global rate limiter(100 req/min)
  • 加了一個 Redis-based sliding window counter
  • 加了 X-RateLimit-RemainingX-RateLimit-Reset headers
  • 加了一個 /api/rate-limit-status endpoint
  • 寫了 migration script 建立 Redis key
  • 總共 ~350 行新 code

問題:我只有一個簡單的 Astro static site,沒有 Express、沒有 Redis、那些 API 是 Cloudflare Workers serverless functions。整個 output 基本上不能用。

Good Spec

## Task: API Rate Limiting for Cloudflare Workers

### Goal

在 /api/contact 和 /api/subscribe 兩個 endpoints 加上 rate limiting,
防止單一 IP 短時間內大量請求。

### Context

- 這是 Astro 5 static site,部署在 Cloudflare Workers
- API endpoints 是 Cloudflare Workers serverless functions
- 沒有 Redis 或任何 external state store
- 目前流量很小(~100 DAU)

### Constraints

- 使用 Cloudflare Workers 的 KV namespace 做計數器(已建立:RATE_LIMIT_KV)
- 不要引入任何 npm dependency
- 只對 POST requests 做 rate limiting
- 限制:每 IP 每分鐘最多 5 次 POST
- 超過限制回 429 Too Many Requests

### Files to modify

- src/pages/api/contact.ts
- src/pages/api/subscribe.ts
- 可以新增一個 src/lib/rate-limit.ts utility

### Verification

1. 從同一 IP 連續 POST 6 次,第 6 次拿到 429
2. 等 60 秒後,再 POST 應該成功
3. 不同 IP 的 rate limit 是獨立的
4. GET requests 不受 rate limit 影響
5. npm run build 通過

Agent 的產出(摘要):

  • 新增 src/lib/rate-limit.ts(~40 行),用 Cloudflare KV 做計數器
  • 修改兩個 endpoint,import rate limiter 並加在 POST handler 前
  • 零 npm dependency
  • 總共 ~80 行新 code
  • 第一次 review 就通過

差異一目了然。不是因為 agent 變聰明了,是因為它拿到了正確的 context 和明確的邊界。

我的 Spec Template(直接拿去用)

## Task: [一句話描述]

### Goal

[3-5 句描述最終結果,不描述實作方式]

### Context

[Agent 需要知道的背景:tech stack、部署環境、相關系統、目前狀態]

### Constraints

- [不要做什麼]
- [技術限制]
- [不碰哪些檔案]
- [行數 / 複雜度 / dependency 上限]

### Files to modify

- [具體的檔案路徑]
- [可以新增什麼檔案]

### Verification Criteria

1. [具體的測試條件 1]
2. [具體的測試條件 2]
3. [build / lint / type check 通過]

### Out of Scope

- [明確列出不屬於這個任務的東西]
- [避免 agent 自己 scope creep]

重點Out of Scope 是最被低估的區塊。它跟 Constraints 不同——Constraints 是「做的時候不要這樣做」,Out of Scope 是「這些事根本不要做」。

例如你在做搜尋功能,Out of Scope 可能包括:

  • 不做 search analytics(之後另外做)
  • 不做 search suggestions / autocomplete
  • 不做搜尋結果的 pagination

這些都是 agent 非常可能「順便」幫你做的東西。提前說不要,省事。

什麼時候不需要 Spec

不是每個任務都需要完整的 spec。回到 Post 1 提到的計畫粒度矩陣:

任務類型Spec 需求範例
Trivial一句話就好「修這個 typo」
Simple2-3 句 + constraint「加 dark mode toggle,用現有的 CSS custom properties」
Medium完整 spec(上面的 template)「加搜尋功能」
ComplexSpec + decomposition「重構 auth system」

過度 spec 跟 spec 不足一樣是浪費。修一個 typo 不需要寫 Goal / Constraints / Verification。判斷的標準是:如果 agent 可能做出你不要的東西,就需要 constraint。如果任務只有一種合理的做法,一句話就夠。

Takeaway

  1. Spec 的品質直接決定 agent 產出的品質——10 分鐘的 spec 可以省 3 小時的收拾。ROI 甜蜜點在 10-15 分鐘,超過 20 分鐘邊際效益遞減。

  2. 好的 spec 有三個要素:Goal(要什麼)、Constraints(不要什麼)、Verification(怎麼驗)。其中 Constraints 和 Out of Scope 是最被低估的——它們防止 agent 做出你不需要的功能。

  3. Task decomposition 的甜蜜點是「一個 reviewable PR」的大小——3-10 個檔案、100-500 行 code、有明確的功能邊界。太粗 agent 自己做太多決策,太細 overhead 超過效益。


上一篇:Context Engineering 深度解析 下一篇:Agent 產出品質保證

留言討論

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