Spec-Driven Development:寫給 Agent 的需求文件,比寫給人的還嚴格
這是「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(太粗):
- 加搜尋功能
Good decomposition:
- 建立搜尋 index:在 build time 從所有 blog posts 產生 JSON search index
- 搜尋 UI component:建立 SearchBar + SearchResults component
- 搜尋邏輯:實作 fuzzy search,支援標題 + 內容 + tags
- 鍵盤導航:Cmd+K 開啟搜尋、方向鍵選擇結果、Enter 導航
- 整合測試:驗證 search index 產生、搜尋結果正確、keyboard navigation
每個 task 都有明確的輸入和輸出。Agent 可以一個一個做,每做完一個你 review 一次。如果第 3 步的搜尋邏輯出了問題,你只需要修那一步,不影響其他的。
AWS Kiro 的 Spec-First 理念
AWS 在 2025 年推出的 Kiro IDE 把 spec-driven 的理念直接建進了工具裡。在 Kiro 裡:
- 你先寫一份結構化的 spec(它叫 “Spec Document”)
- Kiro 自動把 spec 拆成 subtasks
- 每個 subtask 自動產生 acceptance criteria
- Agent 按照 subtask 順序執行,每步完成後自動跑 acceptance tests
雖然我主要用 Claude Code 不用 Kiro,但它的核心理念值得學習:把 spec 當成第一公民,而不是附帶產物。
對照實驗:Bad Spec vs Good Spec
讓我用一個更技術的例子——「在 API 上加 rate limiting」。
Bad Spec
幫我在 API 上加 rate limiting。
Agent 的產出(摘要):
- 引入了
express-rate-limitnpm package - 在所有 API endpoints 上加了 global rate limiter(100 req/min)
- 加了一個 Redis-based sliding window counter
- 加了
X-RateLimit-Remaining和X-RateLimit-Resetheaders - 加了一個
/api/rate-limit-statusendpoint - 寫了 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」 |
| Simple | 2-3 句 + constraint | 「加 dark mode toggle,用現有的 CSS custom properties」 |
| Medium | 完整 spec(上面的 template) | 「加搜尋功能」 |
| Complex | Spec + decomposition | 「重構 auth system」 |
過度 spec 跟 spec 不足一樣是浪費。修一個 typo 不需要寫 Goal / Constraints / Verification。判斷的標準是:如果 agent 可能做出你不要的東西,就需要 constraint。如果任務只有一種合理的做法,一句話就夠。
Takeaway
-
Spec 的品質直接決定 agent 產出的品質——10 分鐘的 spec 可以省 3 小時的收拾。ROI 甜蜜點在 10-15 分鐘,超過 20 分鐘邊際效益遞減。
-
好的 spec 有三個要素:Goal(要什麼)、Constraints(不要什麼)、Verification(怎麼驗)。其中 Constraints 和 Out of Scope 是最被低估的——它們防止 agent 做出你不需要的功能。
-
Task decomposition 的甜蜜點是「一個 reviewable PR」的大小——3-10 個檔案、100-500 行 code、有明確的功能邊界。太粗 agent 自己做太多決策,太細 overhead 超過效益。
上一篇:Context Engineering 深度解析 下一篇:Agent 產出品質保證