WebConf 2025 延伸閱讀:Vue.js 與 AI 協作的開發新思維
繼 JSDC 2025 之後,今天在 WebConf 2025 又講了一場「AI 只懂 React?Vue.js 也能 Vibe Coding!」。
事實上,演講標題裡的 React 只是個引子,我知道你們想看什麼,但不是你們想的那樣 XD
這場演講真正想跟大家聊的,是 AI 協作時代下開發思維的轉變。 而最近在社群竄起的 SDD(Spec-Driven Development)剛好跟我一直以來的理念相當合拍,於是就有了這場延續 JSDC 的分享。
WebConf 當天雖然有將近 50 分鐘,但要把 SDD 從概念講到落地還是有點趕,很多實作細節只能快速帶過就跳下一張投影片, 趁著剛講完記憶還熱,我想補充一些沒講到的重點,並且把簡報重點整理一下,方便回顧。
廢話不多說,先上簡報:
本講演の <spec> のコンセプトは、@ykoizumi0903 さんの記事「Vue SFC のカスタムブロックと AI 駆動開発の相性が良い理由」から着想を得ました。
記事では、Spec-Driven Development の二つの課題として「仕様の言語化の難しさ」と「仕様とコードの分離による AI コンテキストの喪失」を指摘されています。Vue SFC の Custom Block を活用することで、仕様と実装を同一ファイルに配置し、AI が常に完全なコンテキストを参照できる点は、まさに目から鱗でした。
私はこの基盤の上に、いくつかの方向性を拡張しました:SDD の4ステップ閉ループフロー、逆同期(Distillation)の概念、レイヤー戦略(コア層/ビジネス層/プレゼンテーション層)、そして既存の CI/CD フローとの統合方法です。
この重要な洞察を共有してくださった ykoizumi0903 さんに心より感謝申し上げます。おかげで Vue コミュニティに具体的かつ実践可能な AI 協働のアプローチが生まれました。
這場演講的 <spec> 概念,最初是受到 @ykoizumi0903 的文章 Vue SFC のカスタムブロックと AI 駆動開発の相性が良い理由 啟發。
他在文中指出 Spec-Driven Development 的兩大痛點:規格難以語言化,以及規格與程式碼分離導致 AI 上下文遺失。而 Vue SFC 的 Custom Block 恰好能解決這個問題,將規格與實作放在同一個檔案,確保 AI 永遠能讀到完整的上下文。這個洞見讓我眼睛一亮。
我在這個基礎上延伸了幾個方向:SDD 的四步閉環流程、反向同步(Distillation)的概念、分層策略(核心層/業務層/展示層),以及與現有 CI/CD 流程的整合方式。 感謝 @ykoizumi0903 提出這個關鍵洞見,讓 Vue 社群有了一個具體可行的 AI 協作方案。
這篇文章我想補充以下幾個重點:
<spec>的環境設定與常見問題- 有 Spec vs 無 Spec 的實際差異
- SDD 落地時的限制與風險(對,任何方法論都有坑)
- 與現有 CI/CD 流程的整合方式
- 團隊導入的漸進式策略
- 會眾 FAQ(設計師 PM 參與、同步機制、跨元件狀態管理等)
環境設定完整版
在開始使用 <spec> 之前,需要完成以下設定。
1. Vite Plugin
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
function ignoreSpecBlock() {
return {
name: 'ignore-spec-block',
transform(code: string, id: string) {
if (id.includes('?vue&type=spec')) {
return { code: 'export default {}' }
}
}
}
}
export default defineConfig({
plugins: [vue(), ignoreSpecBlock()]
})這個 Plugin 的作用是:當 Vite 遇到 <spec> 區塊時,直接回傳空物件。Production build 不會包含任何 spec 內容,實現零執行成本。
2. TypeScript 宣告
// src/shims-spec.d.ts
declare module 'vue' {
interface SFCCustomBlocksOptions {
spec?: string
}
}
export {}加上這個宣告,Volar 就不會對 <spec> 報紅字警告。
注意最後的 export {}:TypeScript 的 .d.ts 檔案如果沒有任何 import 或 export,會被視為 script(全域腳本);加上 export {} 後才會被視為 module,此時 declare module 'vue' 才能正確作為 module augmentation 擴充 Vue 的型別定義。少了這行,vue-tsc 可能會報錯。
感謝 ShunnNet 留言指正!
3. IDE 設定(VS Code + Vue - Official Extension,原 Volar)
在 <spec> 標籤加上 lang="md",可以獲得 Markdown 語法高亮:
<spec lang="md">
# UserCard
## Props
- user: { name: string, role: 'admin' | 'user' }
</spec>4. ESLint 設定(視情況加入)
通常 ESLint 會自動忽略未知的 Custom Block,不需要額外設定。
但如果你安裝了某些嚴格的 Parser 或 Markdown 檢查 Plugin 導致報錯,可以針對性調整。注意:vue/comment-directive 規則主要處理 <!-- eslint-disable --> 等註解,關閉它可能導致行內 ESLint 忽略功能失效。
{
"overrides": [
{
"files": ["*.vue"],
"rules": {
// 僅在遇到解析錯誤時才考慮加入
// "vue/comment-directive": "off"
}
}
]
}建議先確認錯誤來源,再決定是否需要此設定。
無 Spec vs 有 Spec:AI 產出的差異
讓 AI 實際寫一個 UserCard 元件,看看有沒有 Spec 的差異。
情境:建立一個 UserCard 元件
需求:顯示使用者卡片,admin 要有特殊標示。
無 Spec 的 Prompt:
幫我寫一個 UserCard 元件,顯示使用者名稱和角色,
admin 要有金色邊框。AI 產出(可能結果):
<template>
<div :class="cardClass">
<h3>{{ user.name }}</h3>
<span>{{ user.role }}</span>
</div>
</template>
<script setup>
const props = defineProps(['user'])
const cardClass = computed(() =>
props.user.role === 'admin' ? 'border-yellow-500' : ''
)
</script>問題:
border-yellow-500而非設計稿的amber-400- 沒有點擊事件
- Props 沒有型別定義
- 每次生成可能不同
有 Spec 的元件:
<spec lang="md">
# UserCard
## Props
- user: { name: string, role: 'admin' | 'user' }
## Behavior
- role='admin' → 金色邊框 (amber-400)
- role='user' → 預設邊框 (gray-200)
## Interaction
- 點擊卡片觸發 `select` 事件,payload: user.id
</spec>
<script setup lang="ts">
// AI 根據 Spec 生成...
</script>AI 產出(穩定結果):
<script setup lang="ts">
interface User {
id: string
name: string
role: 'admin' | 'user'
}
const props = defineProps<{ user: User }>()
const emit = defineEmits<{ select: [id: string] }>()
const borderClass = computed(() =>
props.user.role === 'admin'
? 'border-amber-400'
: 'border-gray-200'
)
</script>
<template>
<div
:class="['border-2', 'rounded-lg', 'p-4', borderClass]"
@click="emit('select', user.id)"
>
<h3>{{ user.name }}</h3>
<span>{{ user.role }}</span>
</div>
</template>差異比較
| 項目 | 無 Spec | 有 Spec |
|---|---|---|
| 一致性 | 每次可能不同 | 穩定產出 |
| 型別安全 | 可能缺少 | 明確定義 |
| Design Token | AI 自己猜 | 指定顏色 |
| 事件定義 | 可能遺漏 | 明確列出 |
| 可維護性 | 改需求要重新描述 | 改 Spec 即可 |
為什麼不用 JSDoc 或註解就好?
可能有人會問:「為什麼不直接用 JSDoc 或程式碼註解來寫 Spec 就好?為什麼要用 <spec> Custom Block?」 這是一個好問題。讓我們比較一下兩者的差異:
| 比較項目 | JSDoc / 註解 | <spec> Custom Block |
|---|---|---|
| 結構化程度 | 純文字,散落在程式碼各處 | Markdown 結構(## Props, ## Behavior) |
| 可定位性 | AI 需要在 // 註解中猜測哪些是規格 | AI 精準定位 <spec> 標籤 |
| 意圖明確性 | 可能是實作註解,也可能是說明 | 標籤本身表示「這是規格」 |
| IDE 支援 | 無特殊處理 | 可加 lang="md" 獲得語法高亮 |
核心差異:在 Vue SFC 中,<spec> 提供了一個「明確標示為規格」的獨立區塊,AI 不需要猜測哪些註解是規格、哪些只是實作說明。
更重要的是 Co-location(空間群聚) 的價值:我們利用 Vue SFC 的特性,將 Spec 與 Code 放在同一個檔案,確保 AI 在處理該元件時,必定能讀取到這份「最高權重的指令」。 這比依賴外部文檔(容易過期、容易被 AI 忽略)或散落在各處的註解更可靠,同時也更易於人工後續維護。
當然,JSDoc 在非 SFC 的場景(如 Store、工具函式、composables)仍然是很好的選擇。 這裡我認為重點不在 「JSDoc vs <spec>」 的兩者對立,而是不同層級下怎麼選用最適合的方式。
LLM 對結構化的 Markdown 遵循能力較高。就像交接工作時,給同事一份條列式 SOP,比在程式碼裡到處寫註解更有效率。
Spec 寫作準則
該寫什麼
| 類別 | 說明 | 範例 |
|---|---|---|
| Props 資料契約 | 輸入的資料結構與型別 | user: { name: string, role: 'admin' | 'user' } |
| Behavior 行為規則 | 條件與結果的對應 | role='admin' → 金色邊框 (amber-400) |
| Interaction 互動定義 | 使用者操作與事件 | 點擊觸發 select 事件,payload 為 user.id |
| 視覺規範 Design Token | 顏色、間距等設計值 | amber-400、gap-4、rounded-lg |
不該寫什麼
| 類別 | 錯誤範例 | 為什麼不該寫 |
|---|---|---|
| 程式庫選擇 | 使用 axios.get 獲取資料 | 屬於專案層(CLAUDE.md) |
| 實作細節 | 使用 ref<boolean>(false) 儲存 isOpen | 換實作方式就要改 |
| 佈局細節 | 使用 flex justify-center 置中 | 這是 CSS 實作 |
| 錯誤處理機制 | 用 try-catch 包住 API 呼叫 | 可能與全域處理衝突 |
判斷原則
兩個問題幫你判斷 Spec 粒度:
- 「換一種實作方式,這段描述需要改嗎?」 → 要改就是太細了
- 「AI 看完這段,還需要問我問題嗎?」 → 會問就是太粗了
Design Token 保留的重要性
反向同步時,很多人會把「看起來像實作」的東西都移除,結果把 amber-400 也移除了。
這是錯誤的。
Design Token 不是實作細節,是視覺規範。它們是 AI 保持 UI 一致性的關鍵。
# 錯誤:移除了 Design Token
## Behavior
- admin 使用者顯示金色邊框
# 正確:保留 Design Token
## Behavior
- role='admin' → 金色邊框 (amber-400)只寫「金色邊框」,AI 可能生成 border-yellow-500、border-amber-300、甚至 border-[#ffd700]。寫明 amber-400,AI 就知道用這個特定顏色。
SDD 的限制與風險
任何方法論都有適用範圍。不然就變成邪教了(笑)。誠實說明這些限制,才能幫你做正確決策。
已知限制
| 限制 | 緩解方式 |
|---|---|
| 團隊需要時間適應「先寫 Spec」 | 漸進式導入,從新元件開始 |
| Spec 寫太詳細會變成負擔 | 遵守「只寫意圖」原則 |
| 跨元件情境粒度難定義 | 以「獨立可測試的單位」為粒度 |
| 不同 AI 工具處理不一致 | 使用規則檔同步機制 |
風險警示
Spec 與 Code 衝突
案例:Spec 寫「用 try-catch 並 alert」,但專案引入了 Axios Interceptor。AI 照 Spec 生成 try-catch,錯誤訊息跳兩次。
解法:Spec 不寫實作機制,只寫意圖(「處理異常,遵循全域規範」)。
測試過度生成
讓 AI 同時生成 Code 和 Test,可能產生大量「為測試而測試」的案例。
解法:在 Spec 中標註「需要測試的關鍵行為」,使用分層策略。
過度依賴 Spec
團隊花太多時間寫 Spec,反而減緩開發。
解法:記住目標是「讓 AI 寫出可控的程式碼」,不是「寫完美的文件」。Spec 夠用就好。
與現有開發流程整合
CI/CD Pipeline
[開發] Spec(人工)→ AI CodeGen → AI TestGen → 本地驗證
[提交] git commit → Pre-commit Hook(檢查 Spec 是否更新)
[CI] PR 建立 → Vitest → Lint → Type Check
[Review] 先看 <spec> 變更 → 再看 Code → 確認 Test 覆蓋PR Review Checklist
在 PR Template 加入:
## SDD Checklist
- [ ] 新增元件是否已加上 `<spec>`?
- [ ] 修改行為時 `<spec>` 是否已同步?
- [ ] 測試是否覆蓋 `<spec>` 定義的關鍵行為?Git Hooks 自動提醒(Node.js 腳本範例)
將此腳本存為 scripts/check-spec.js,並在 .husky/pre-commit 中以 node scripts/check-spec.js 執行:
// scripts/check-spec.js
const { execSync } = require('child_process')
const changedFiles = execSync('git diff --cached --name-only')
.toString().split('\n')
changedFiles
.filter(f => f.endsWith('.vue'))
.forEach(file => {
const diff = execSync(`git diff --cached ${file}`).toString()
const hasCodeChanges = diff.includes('<script') || diff.includes('<template')
const hasSpecChanges = diff.includes('<spec')
if (hasCodeChanges && !hasSpecChanges) {
console.warn(`⚠️ ${file}: 修改了程式碼但沒更新 <spec>`)
}
})注意:此腳本較為嚴格。在純重構(Refactoring)不改變行為的情況下,可能會出現誤報。建議團隊約定在 commit message 加入特定 tag(如 [refactor])來略過此檢查,或將腳本改為僅輸出提醒而非阻擋 commit。
反向同步(Distillation)實戰指南
當你修改了程式碼但忘記更新 Spec,或者接手一個沒有 Spec 的舊元件,就需要「反向同步」:從程式碼萃取出 Spec。 這裡的重點是抽象出「意圖」,而非「實作細節」。
實際操作步驟
Step 1:給 AI 的 Prompt
請閱讀這個元件的實作,萃取出 <spec> 區塊。
規則:
1. 只寫「意圖」,不寫「實作方式」
2. 保留 Design Token(如 amber-400)
3. 使用 Props / Behavior / Interaction 三段式結構
4. 移除任何程式庫相關描述(如 axios、pinia)
5. 如果程式碼中包含可疑邏輯或潛在 Bug,請標註為 (待確認),不要直接視為正規行為Step 2:AI 產出的初稿
假設元件程式碼是:
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useUserStore } from '@/stores/user'
interface Props {
userId: string
showBadge?: boolean
}
const props = withDefaults(defineProps<Props>(), {
showBadge: true
})
const emit = defineEmits<{
click: [userId: string]
delete: [userId: string]
}>()
const userStore = useUserStore()
const user = computed(() => userStore.getUserById(props.userId))
const isAdmin = computed(() => user.value?.role === 'admin')
</script>
<template>
<div
class="p-4 rounded-lg border-2"
:class="isAdmin ? 'border-amber-400 bg-amber-50' : 'border-gray-200'"
@click="emit('click', userId)"
>
<span v-if="showBadge && isAdmin" class="badge-admin">Admin</span>
<h3>{{ user?.name }}</h3>
<button @click.stop="emit('delete', userId)">刪除</button>
</div>
</template>AI 萃取結果:
# UserCard
## Props
- userId: string - 使用者 ID
- showBadge: boolean (default: true) - 是否顯示角色徽章
## Behavior
- 根據 userId 從 store 取得使用者資料
- role='admin' → 金色邊框 (amber-400) + 淺金背景 (amber-50)
- role='admin' 且 showBadge=true → 顯示 Admin 徽章
- 其他角色 → 灰色邊框 (gray-200)
## Interaction
- 點擊卡片 → 觸發 `click` 事件,payload: userId
- 點擊刪除按鈕 → 觸發 `delete` 事件,payload: userId(不冒泡)Step 3:人工審查與調整
AI 萃取的 Spec 需要人工確認:
- Design Token 有保留
- 事件定義完整
- 「根據 userId 從 store 取得」可能太實作導向,改成「根據 userId 顯示對應使用者」
反向同步能自動化嗎?
短答案:半自動化可行,全自動化風險高。
可自動化的部分
- 偵測變更:Git Hook 可以偵測「程式碼改了但 Spec 沒改」
- 生成初稿:AI 可以從程式碼萃取 Spec 草稿
- 格式檢查:確認 Spec 包含必要區塊(Props / Behavior / Interaction)
不建議自動化的部分
- 直接覆蓋 Spec:AI 萃取可能包含實作細節,需要人工過濾
- 語意判斷:「這是意圖還是實作?」需要人類判斷
- 業務邏輯確認:AI 不知道某個行為是 Bug 還是 Feature
建議的半自動化流程
# .husky/pre-commit(提醒模式)
# 注意:這是概念範例,實際使用請根據專案現況調整
#!/bin/sh
changed_vue_files=$(git diff --cached --name-only | grep '\.vue$')
for file in $changed_vue_files; do
# 檢查是否有 <spec> 區塊
if ! grep -q '<spec' "$file"; then
echo "⚠️ $file 沒有 <spec> 區塊"
continue
fi
# 檢查 spec 是否有更新
diff=$(git diff --cached "$file")
has_code_change=$(echo "$diff" | grep -E '<script|<template')
has_spec_change=$(echo "$diff" | grep '<spec')
if [ -n "$has_code_change" ] && [ -z "$has_spec_change" ]; then
echo "⚠️ $file: 程式碼有變更但 <spec> 沒更新"
echo " 執行 'pnpm spec:sync $file' 生成建議"
fi
done// scripts/spec-sync.js(生成建議,不自動覆蓋)
import Anthropic from '@anthropic-ai/sdk'
import fs from 'fs'
const client = new Anthropic()
async function generateSpecSuggestion(filePath) {
const content = fs.readFileSync(filePath, 'utf-8')
const message = await client.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 1024,
messages: [{
role: 'user',
content: `請從這個 Vue 元件萃取 <spec>,只寫意圖不寫實作:\n\n${content}`
}]
})
console.log('=== 建議的 Spec ===')
console.log(message.content[0].text)
console.log('\n請人工審查後貼入元件')
}為什麼不建議全自動化?
反向同步的核心價值在於「思考這段程式碼的意圖是什麼」。
如果完全自動化:
- Spec 會變成「程式碼的另一種描述」而非「意圖的規格」
- 失去人類審查,錯誤的行為會被記錄成「正確的 Spec」
- Bug 會被當成 Feature 寫進 Spec
最佳實踐:自動化偵測 + AI 生成建議 + 人工審查確認。
記住,Spec 是給 AI 的「工作指令」,不是給版控的「程式碼描述」。讓機器做機器擅長的事,人做人擅長的事。
決策樹:該用 SDD 嗎?
不是每個元件都需要 Spec。不然光寫 Spec 就飽了。這裡提供一個簡單的決策流程:
這個元件需要寫 <spec> 嗎?
│
├─ 跟「錢」有關?(金流、付款、訂單)
│ └─ YES → Spec + Test
│
├─ 會被多處重用?
│ └─ YES → Spec Only
│
├─ 有複雜的條件邏輯?
│ └─ YES → Spec Only
│
├─ 需要長期維護?
│ └─ YES → Spec Only
│
└─ 以上皆否 → 直接 Vibe Coding元件分類參考
| 元件類型 | 策略 | Test | 理由 |
|---|---|---|---|
| PaymentForm | Spec + Test | Yes | 金流,錯誤代價高 |
| LoginForm | Spec + Test | Yes | 權限,安全性重要 |
| UserCard | Spec Only | Optional | 可重用,但非核心 |
| DataTable | Spec Only | Optional | 複雜互動 |
| PageHeader | Vibe Coding | No | 純展示 |
| AboutPage | Vibe Coding | No | 一次性頁面 |
團隊導入策略
說真的,最難的不是技術,是說服團隊。「又來一個新東西要學?」這種反應很正常。
漸進式導入時程
Phase 1(第 1-2 週)
└─ 只在「新元件」使用 SDD
└─ 指定 1-2 位 Champion 先試水溫
Phase 2(第 3-4 週)
└─ 重構舊元件時順便加 Spec
└─ 建立 Spec 範本,降低撰寫門檻
Phase 3(第 5 週起)
└─ PR Review 開始檢查 Spec
└─ 每週五花 30 分鐘,AI 批次檢查同步狀態Spec 範本
<spec>
# [元件名稱]
## Props
- prop1: type - 說明
## Behavior
- 條件 → 結果 (design-token)
## Interaction
- 動作觸發 `event-name` 事件,payload: { ... }
</spec>會眾 FAQ
演講結束後收到不少問題,這裡整理幾個比較常見的,順便補充一些在簡報裡來不及講的內容。
Q1: 設計師和 PM 需要參與 Spec 撰寫嗎?
還是純粹是工程師的工作?
我的看法是:Spec 的撰寫責任在工程師,但內容來源不只是工程師。
設計師和 PM 不需要學會寫 <spec> 語法,但他們提供的資訊會直接影響 Spec 的品質:
| 角色 | 貢獻 | 範例 |
|---|---|---|
| 設計師 | Design Token、視覺規範 | 「這個按鈕用 amber-400,不是 yellow-500」 |
| PM | 業務邏輯、Edge Cases | 「admin 可以刪除,但要二次確認」 |
| 工程師 | 整合成結構化 Spec | 把上述資訊寫成 Props / Behavior / Interaction |
實務上的做法是:工程師寫完 Spec 初稿後,拉設計師和 PM 快速 Review 一下「這是你們要的嗎?」。這個過程通常 5 分鐘就結束,但可以避免後面來回修改。
如果你的團隊設計師或 PM 比較積極,也可以讓他們直接在 Spec 上面用註解補充,工程師再整理成正式格式。重點是「資訊要流通」,誰來寫只是執行細節。
Q2: Spec 跟 Code 不同步怎麼辦?有沒有強制機制?
講者提到「趕時間直接改 Code 忘記更新 Spec」是現實,有沒有強制機制防止這種情況?
有,但我建議「提醒優先,阻擋其次」。
文章前面有提到 Git Hook 的做法,這裡補充幾個層級:
Level 1:提醒(推薦先從這裡開始)
# pre-commit hook 只 warn,不阻擋
echo "⚠️ 這些 .vue 檔案改了程式碼但沒更新 <spec>,請確認是否需要同步"好處是不會打斷開發節奏,讓工程師自己判斷「這次改動是否影響行為」。純重構、改變數名稱這種不改行為的修改,本來就不需要更新 Spec。
Level 2:PR Review 檢查
在 PR Template 加入 Checklist:
- [ ] 行為有變更時,`<spec>` 已同步更新這是人工檢查,但有紀錄可追蹤。
Level 3:CI 阻擋(謹慎使用)
可以在 CI 裡跑一個 script 檢查「有改 <script> 或 <template> 但沒改 <spec>」的檔案,然後 fail 掉。但這會有誤報(重構不改行為的情況),所以要搭配 escape hatch,例如在 commit message 加 [no-spec] 可以跳過檢查。
我的建議
從 Level 1 開始,觀察一兩週看看漏掉的頻率。如果團隊自律性夠高,Level 1 就夠了;如果經常漏,再升級到 Level 2。Level 3 通常只有在「這個元件出錯代價很高」(例如金流相關)才需要。
話說,如果工程師連提醒都無視,那問題可能不在工具上(笑)
Q3: AI 反覆修正會不會比自己寫還慢?什麼時候該放棄?
如果 AI 生成的結果需要反覆修正,會不會比自己寫還慢?
會,而且這是真實會發生的情況。
我自己的判斷標準是「三次原則」:第一次 AI 產出不對,先調整 Prompt 或 Spec;第二次還是不對,檢查是不是 Context 不夠,Rules 檔案有沒有相關規則;如果第三次還是不對,就直接手寫吧。
三次修正的時間,通常已經超過自己寫的時間了。繼續跟 AI 糾纏下去只會更浪費時間。
什麼情況 AI 特別容易卡住?
| 情境 | 原因 | 建議 |
|---|---|---|
| 複雜的狀態邏輯 | AI 難以追蹤多層狀態變化 | 自己寫核心邏輯,AI 寫周邊 |
| 專案特有的 Pattern | AI 沒見過你們的寫法 | 先在 Rules 放範例 |
| Edge Case 處理 | AI 傾向 Happy Path | 手動補充邊界條件 |
| 效能敏感的程式碼 | AI 不知道你的效能瓶頸 | 自己寫,AI 只做 Review |
換個角度想
「AI 生成 → 修正 → 再生成」這個過程,其實也是在幫你釐清需求。有時候 AI 寫錯,反而讓你發現「原來我自己也沒想清楚這邊要怎麼處理」。
所以即使最後選擇手寫,這個過程也不算完全浪費。只是心情會有點煩躁
Q4: 跨元件的複雜互動怎麼處理?
簡報展示的是單一元件的 Spec,如果是跨多個元件的狀態管理(例如 Pinia),Spec 要怎麼寫?
這是個好問題,因為 <spec> 的設計確實是以「單一元件」為單位。跨元件的情況需要用不同的方式處理。
我的分層策略
| 層級 | 放哪裡 | 內容 |
|---|---|---|
| 專案層 | CLAUDE.md | Store 的整體架構、命名規範 |
| 模組層 | Store 檔案的 JSDoc | 這個 Store 的職責、對外 API |
| 元件層 | <spec> | 這個元件如何使用 Store |
實際範例
// stores/cart.ts
/**
* @module CartStore
*
* ## 職責
* - 管理購物車商品列表
* - 計算總價(含折扣邏輯)
* - 與後端同步購物車狀態
*
* ## 對外 API
* - items: 商品列表(唯讀)
* - totalPrice: 總價(含折扣)
* - addItem(product, quantity): 加入商品
* - removeItem(productId): 移除商品
* - checkout(): 結帳,回傳 orderId
*
* ## 狀態流
* 1. addItem → 更新 items → 觸發 totalPrice 重算
* 2. checkout → 呼叫 API → 清空 items → 回傳 orderId
*/
export const useCartStore = defineStore('cart', () => {
// ...
})然後在元件的 <spec> 裡這樣寫:
<spec lang="md">
# CartButton
## Dependencies
- 使用 CartStore 的 items 和 addItem
## Behavior
- 點擊 → 呼叫 CartStore.addItem(product, 1)
- items.length > 0 → 顯示紅點數字
</spec>為什麼不把所有東西都寫在元件 Spec 裡?
因為會重複。如果 10 個元件都用到 CartStore,你不會想在 10 個地方都寫一次 Store 的完整規格。
元件的 Spec 只需要說明「這個元件怎麼用這個 Store」,Store 本身的規格放在 Store 檔案的 JSDoc 或獨立文件。
Q5: 團隊成員技術水平不一,怎麼確保 Spec 品質一致?
講者提到 Spec 寫太細或太粗都有問題,有沒有具體的 review checklist?
有,這是我目前實驗中整理出來的 Spec Review Checklist,供參考:
Spec Review Checklist
## 結構完整性
- [ ] 有 Props 區塊(如果元件接收 props)
- [ ] 有 Behavior 區塊(描述條件與結果)
- [ ] 有 Interaction 區塊(如果有使用者互動)
## 粒度檢查
- [ ] 沒有寫到「用什麼函式/API」(太細)
- [ ] 沒有寫到「怎麼實作」(太細)
- [ ] 有寫到「Design Token」(視覺規範要保留)
- [ ] 看完 Spec 後,AI 不需要再問問題(太粗的話會需要)
## 品質檢查
- [ ] 使用「條件 → 結果」格式描述行為
- [ ] 事件有說明 payload 結構
- [ ] 沒有模稜兩可的描述(如「適當處理」「必要時」)實務上怎麼推動?
先從範本開始,前面文章有提供 Spec 範本,讓團隊成員照著填空就好。前幾次一起 Review,資深成員帶著 Junior 一起看,解釋為什麼這樣寫。然後建立 Bad Examples,收集「寫太細」和「寫太粗」的反例,讓大家知道邊界在哪。
一個簡單的判斷口訣
「換一種寫法,Spec 要改嗎?」
- 要改 → 太細了,刪掉
- 不用改 → 可以保留
比如「使用 ref<boolean>(false)」要改成「使用 reactive」的話 Spec 就要改,所以這句太細了。 但「admin 顯示金色邊框 (amber-400)」換成用 CSS Variable 實作,Spec 也不用改,所以可以保留。
Q6: 既有專案怎麼辦?
我們現有的 Vue 專案沒有
<spec>區塊,要從哪裡開始導入?全面重寫還是漸進式?
絕對不要全面重寫。除非你想讓團隊集體崩潰
漸進式導入的策略:
Phase 1:只對新元件使用
從今天開始,所有新建立的元件都加上 <spec>。舊的不動。
這是最低成本的起步方式,團隊可以先熟悉 Spec 的寫法,累積經驗。
Phase 2:修 Bug 時順便加
當你要修某個舊元件的 Bug,先花 5 分鐘讓 AI 幫你反向同步(Distillation)出 Spec 初稿,人工審核後加進去。
這樣 Spec 會隨著日常維護慢慢覆蓋到舊程式碼,而且是從「最常被改動的元件」開始,ROI 最高。
Phase 3:重點元件優先
如果想更主動一點,可以列出專案裡的「核心元件」,像是被很多地方引用的共用元件、跟金流或權限相關的元件、經常出 Bug 的元件,這些優先加上 Spec + Test。
實際時程建議
| 週數 | 目標 |
|---|---|
| 1-2 週 | 新元件 100% 有 Spec,指定 1-2 人當 Champion |
| 3-4 週 | 建立 Spec 範本和 Review Checklist,開始在 PR Review 檢查 |
| 5-8 週 | 逐步對核心元件補 Spec(每週 2-3 個) |
| 之後 | 維持習慣,隨日常維護自然覆蓋 |
千萬不要做的事
不要一次性把所有元件都補上 Spec,浪費時間而且品質不會好。也不要強制要求兩週內全部完成,團隊會反彈。更不要沒有 Champion 就開始推,你需要有人負責回答問題、做示範。
心態調整
Spec 覆蓋率不是目標,「讓 AI 產出可控」才是目標。
如果某個元件本來就很穩定、很少改動,沒有 Spec 也不會怎樣。把精力放在「會被頻繁改動」和「出錯代價高」的元件上。
結語
寫這篇文章的過程中,我一直在想一件事:SDD 的核心價值到底是什麼?
不是讓 AI 寫出更多程式碼,而是讓我們用更少的時間產出更可控的結果。
Spec 本質上是一種「意圖的結晶」。當你把腦中模糊的需求寫成結構化的 Spec,其實就是在強迫自己想清楚「我到底要什麼」。這個過程本身就有價值,就算不用 AI,這份 Spec 也能幫助團隊溝通、幫助 Code Review、幫助未來的自己回想當初的設計意圖。
說到底,Spec 的本質是溝通。它不只是寫給 AI 看的提示詞,也是寫給人類接手者看的規格書。即使哪天不用 AI 了,良好的規格描述本來就是高品質程式碼的一部分。
我在演講的時候也提到,工程師天生就討厭寫文件,也討厭別人不寫文件。然後自己不看文件,也討厭別人不看文件。 這個部分正好由 AI 來幫忙承擔。只要我們願意花點時間「先寫 Spec」,AI 就能幫我們把這些 Spec 轉化成程式碼和測試,減少人類撰寫的負擔。
雖然流程增加了「撰寫規格」的成本,本質上是用「前期思考時間」來換取「後期 Debug 時間」。 這筆交易是否划算,我認為取決於團隊的專案複雜度與團隊的成員素質。
對於快速迭代的原型期專案,這可能是負擔;但對於需要長期維護的核心業務,這筆投資通常是值得的。
所以與其說 SDD 是一種「AI 開發方法論」,不如說它是一種思考方式的轉變:從「我要寫什麼程式碼」變成「我要表達什麼意圖」。
SDD 某種程度上是針對「當前 AI 能力限制」所設計的工程解法。隨著 AI 持續進步,未來或許不需要這麼明確的結構化 Spec。但 Spec-first 的思維方式本身有獨立價值,它迫使我們在動手之前先想清楚意圖,這個習慣不會因為工具進步而過時。
這個轉變不容易,但值得嘗試。
相關資源
如有問題或建議,歡迎留言討論!