Designing Fields — 欄位設計指引
本文件為「設計欄位」情境的完整指引。適用於 ticket 模板、YAML frontmatter、API response、database schema、配置檔案等任何多欄位結構的設計。
為什麼獨立成篇:欄位設計的錯誤會被模板放大。一個設計不良的 ticket 模板會產生上百個混淆 ticket、上千則語意空洞的資料。欄位一旦上線就難以撤回,因為後續所有資料都已按此格式寫入。
自包含聲明:閱讀本文件不需要先讀其他 reference。核心原則的精要在本文件內展開於「欄位設計」情境;需要跨情境套用時,才去讀對應的
writing-*.md。
TL;DR(30 秒版本)
- 每個欄位承載一個維度,不同欄位描述同一件事的不同面向(what 描述動作、why 陳述動機、acceptance 定義可驗證條件)。
- frontmatter 欄位為程式化查詢服務,ID 格式、enum 值、布林命名要穩定可 grep。
- 欄位名稱暗示其問什麼問題(
why問動機,how問策略,blockedBy問阻塞關係)。 - 欄位值格式一致,enum 有限集優於自由文字,複合值用穩定分隔符。
- 新增欄位前問七個問題(見「新增欄位的決策框架」章節),避免欄位膨脹。
目錄
- 原子化 × 欄位:每個欄位承載一個維度
- 索引 × 欄位:程式化查詢、ID 格式、frontmatter 設計
- 意圖顯性 × 欄位:欄位名稱即提問
- 可查詢性 × 欄位:值格式一致性、enum 命名
- 欄位設計 meta:新增欄位的決策框架
- Ticket 六欄位角度解析(六欄位總表 + 詳細範例連結)
- 非 ticket 情境:YAML 配置、API response
1. 原子化 × 欄位:每個欄位承載一個維度
原則:每個欄位只回答一個問題。若一個欄位同時承載兩個維度,填寫者會混淆,查詢者會誤讀。
判斷標準
一個欄位的內容若出現以下徵兆,代表「承載太多維度」,應拆分:
| 徵兆 | 範例 | 拆分方式 |
|---|---|---|
| 描述需要「和」連接 | status: "in_progress 且 blocked by API" | 拆成 status + blockedBy |
| 同一欄位混用動作與動機 | what: "修 bug 因為用戶回報崩潰" | 拆成 what(修 bug)+ why(用戶回報崩潰影響可用性) |
| 單欄位塞多個可查詢值 | tags: "p0 security urgent" | 改為 priority: p0 + category: security + urgency: high |
| 用自由文字藏結構資料 | notes: "owner=alice, due=2026-04-20" | 拆成 owner: alice + due: 2026-04-20 |
反例:一個欄位承載兩個維度
1# 錯誤:status 同時表達「進度」和「阻塞原因」
2status: "in_progress_waiting_for_api_team"問題:
- 查詢「所有 in_progress 的 ticket」需要字串前綴比對,不穩定。
- 改阻塞對象時,狀態字串也要改,污染查詢歷史。
- 報表無法分別統計「進度分佈」和「阻塞分佈」。
正確:兩個維度各自一個欄位
1status: in_progress
2blockedBy:
3 - team: api
4 reason: "等待 /v2/users 端點上線"每個欄位只回答一個問題:status 回答「目前在哪個階段」,blockedBy 回答「被什麼擋住」。
原子化測試
拿一個欄位問三個問題:
- 這個欄位回答什麼問題? — 若答案需要「和」連接兩個問題,必須拆分。
- 這個欄位的值能獨立變更嗎? — 若必須連動其他資訊,代表混在一起了。
- 能用此欄位單獨做統計/排序嗎? — 若不能,代表它混入了其他維度。
2. 索引 × 欄位:程式化查詢、ID 格式、frontmatter 設計
原則:frontmatter 欄位存在的理由之一是讓程式能查詢。人眼看不出結構,程式才看得出。所以 ID 格式、enum 值、布林欄位的命名要為「程式化查詢」服務。
ID 格式設計
好的 ID 同時對人和程式友善。以下是穩定 ID 格式的檢查點:
| 要求 | 正確範例 | 錯誤範例 | 原因 |
|---|---|---|---|
| 分層結構可拆解 | v1.2.0-W03-021.4 | ticket_74_sub7 | 前者可用分隔符拆成版本/wave/序號/子序號 |
| 穩定分隔符 | 用 - 分層、. 分子層 | 混用 _、-、 | 程式 regex 才能一致比對 |
| 固定位數(可選) | P001、P002 | P1、P10、P100 | 字典序排序才會等於數值序 |
| 不含空白與特殊字元 | prop-cache-cleanup | prop cache cleanup! | 避免需要引號與跳脫 |
| 可預測的命名空間 | PROP- / UC- / SPEC- 前綴 | 無前綴純數字 | 能靠前綴快速過濾類別 |
frontmatter 為查詢服務
frontmatter 的目的是「讓特定查詢變快」、不是「把資訊塞進去」 — 因為光把資訊塞進去只是換個地方存、查詢仍然要 scan;要查詢變快、欄位要做特定設計(穩定鍵名、可索引的值類型、前綴命名空間)才能匹配查詢工具的索引機制。把目的搞錯會做出「資訊完整但查詢仍要全文掃」的 frontmatter、讀者得到結果但工具拿不到效益。
1---
2id: ticket-001
3title: "修復登入崩潰"
4status: in_progress
5priority: P0
6blockedBy: []
7version: v1.2.0
8wave: 3
9---每個欄位服務一種查詢需求:
| 欄位 | 服務什麼查詢 |
|---|---|
status | 列出所有「進行中」的項目 |
priority | 依優先順序排序 |
blockedBy | 找出被阻塞的項目 |
version / wave | 按發佈批次篩選 |
欄位不服務的查詢就不要存。例如「建立者的辦公座位」放在 frontmatter 只會污染欄位密度。
為「程式能看」優先,為「人能看」是加分
frontmatter 的值優先讓程式 grep/parse,再由顯示層(UI、報表)翻譯給人。
| 優先程式 | 優先人 |
|---|---|
status: in_progress | status: "進行中(已派發工程師)" |
priority: P0 | priority: "緊急 — 阻塞發佈" |
blockedBy: [ticket-042] | blockedBy: "等 ticket-042 的 API 做完" |
若需要給人看的補充描述,用獨立的自由文字欄位承接(如 blockedByReason),frontmatter 主欄位保持結構化。
3. 意圖顯性 × 欄位:欄位名稱即提問
原則:欄位名稱本身就是一個問題。填寫者看到欄位名,應立刻知道要填什麼答案。若欄位名需要額外文件解釋,代表命名不夠顯性。
好欄位名 = 好問題
| 欄位名 | 它問的問題 | 填寫者的思考 |
|---|---|---|
what | 「做了什麼?」 | 描述動作/內容 |
why | 「為什麼需要?」 | 陳述動機/業務理由 |
when | 「什麼時候觸發?」 | 條件/時機 |
where | 「影響範圍?」 | 檔案/模組/層級 |
how | 「怎麼做?」 | 實作策略 |
acceptance | 「怎樣算完成?」 | 可驗證條件 |
blockedBy | 「被什麼擋住?」 | 依賴項目 |
owner | 「誰負責?」 | 單一責任人 |
deprecatedAt | 「何時廢棄?」 | 日期或版本號 |
反例:名稱無法暗示問題
| 模糊欄位名 | 問題 | 改善 |
|---|---|---|
data | 什麼資料? | payload / userProfile / metrics 依內容 |
info | 什麼資訊? | description / errorDetail / author |
meta | 描述什麼? | createdBy / source / tags |
config | 配置什麼? | retryPolicy / cacheTtl |
flag | 什麼旗標? | isPublished / hasWarning |
type | 什麼類型? | eventType / userRole / errorCategory |
布林欄位用 is_ / has_ / can_ 開頭
| 錯誤 | 正確 | 原因 |
|---|---|---|
active | isActive | 看到就知道是 true/false |
permission | hasPermission | 暗示「擁有關係」 |
edit | canEdit | 暗示「能力檢查」 |
visible | isVisible | 避免與名詞混淆 |
欄位名稱體現抽象層
一份文件內的欄位應處於同一抽象層。混層會讓讀者不知道該看哪個。
1# 錯誤:混抽象層
2what: "修 bug" # 業務層
3implementationDetail: "修改 auth.py 第 42 行" # 實作層改善:what 留在業務層,實作細節進 how 或 where.files。
4. 可查詢性 × 欄位:值格式一致性、enum 命名
原則:同一個欄位的值要有穩定格式,讓 grep/filter/sort 可預測。格式不一致會讓查詢需要 N 個 regex 才能覆蓋,最終退化成「用肉眼翻」。
enum 優於自由文字
有限集合的欄位應列出所有合法值:
1# 正確:status 有固定集合
2status: in_progress # 僅限 pending / in_progress / blocked / completed / cancelled
3
4# 錯誤:狀態用自由文字
5status: "進行中,但有點卡"enum 的三個好處:
- grep 精確(
status: in_progress無歧義) - 值變更能被編譯器/驗證器捕捉
- 統計時不用做語意聚類
enum 命名規則
| 要求 | 正確 | 錯誤 |
|---|---|---|
全小寫、單字用 _ 連接 | in_progress | inProgress / In-Progress |
| 語意中立(不帶情緒) | cancelled | abandoned_by_team |
涵蓋周延(加 unknown 或 other 兜底) | unknown | 漏 fallback 導致必填卡關 |
| 避免數字後綴(除非真的有序) | high / medium / low | level1 / level2 |
複合值的穩定分隔符
若欄位必須承載複合值,用穩定的分隔符讓 regex 可拆。
1# 正確:用 : 分隔方向和目標
2direction: "to-sibling:ticket-045"
3
4# 錯誤:自由文字
5direction: "指向兄弟 ticket 045"常見分隔符慣例:
| 分隔符 | 用途 | 範例 |
|---|---|---|
: | 維度:值 | scope:api、type:bug |
/ | 路徑 | src/auth/login.py |
, | 多值列表(若不用陣列) | a11y,i18n,perf |
→ / -> | 流向 | pending → in_progress |
禁止混用分隔符。若一個欄位用 :,整個系統都要用 :。
欄位值前綴做分類
當同類型但需細分時,用固定前綴而非混在自由文字中。
1# 正確:ID 前綴暗示類別
2id: PROP-042 # proposal (portability-allow: teaching example)
3id: UC-007 # use case
4id: SPEC-012 # spec
5
6# 錯誤:自由文字描述類別
7id: "proposal 42"日期時間統一 ISO 8601
1# 正確
2createdAt: "2026-04-16"
3startedAt: "2026-04-16T09:30:00+08:00"
4
5# 錯誤
6createdAt: "2026/4/16" # 分隔符不穩
7createdAt: "April 16, 2026" # 不可排序
8createdAt: "昨天" # 不可重複解析5. 欄位設計 meta:新增欄位的決策框架
原則:欄位一旦加入就難以撤回。新增前必問以下七個問題,避免欄位膨脹、語意重疊。
新增欄位前必問清單
| # | 問題 | 若答「否」的處理 |
|---|---|---|
| 1 | 這個欄位回答什麼問題?(單一維度) | 若回答多個問題 → 拆成多欄位 |
| 2 | 是否已有欄位涵蓋同一維度? | 若有 → 擴充既有欄位或重新命名,不新增 |
| 3 | 至少有一個查詢情境需要它嗎? | 若無 → 放入自由文字 notes,不進 frontmatter |
| 4 | 值域是否有限可枚舉? | 若是 → 用 enum;若否 → 確認自由文字真的必要 |
| 5 | 缺省值(missing)有明確語意嗎? | 若無 → 補上預設值或註明 nullable |
| 6 | 填寫者有能力正確填寫嗎? | 若否 → 改成程式自動填,或提供 enum 選單 |
| 7 | 停用時如何淘汰? | 若無計畫 → 先標註 deprecatedAt 欄位策略 |
欄位語意重疊檢查
新增欄位時最常見的錯誤是「與既有欄位語意重疊」。檢查方式:
- 列出新欄位的提問(它要回答什麼問題)。
- 對照既有欄位的提問,找是否有重複。
- 若有重複,選其一:
- 合併:新增的維度其實可以塞進既有欄位(若不違反原子化)。
- 改名:讓兩個欄位的提問有明確區別。
- 捨棄:若既有欄位已能滿足,不新增。
欄位膨脹的警訊
| 警訊 | 意義 | 行動 |
|---|---|---|
| 一個 schema 超過 15 個 frontmatter 欄位 | 單一物件承載過多責任 | 分拆為子物件(nested)或獨立 schema |
| 有些欄位在 > 80% 的資料中為空 | 該欄位的查詢需求薄弱 | 降級為自由文字 notes |
| 欄位的填寫規則寫在多份 wiki | 規則太複雜 | 改為 enum 或引入驗證器 |
| 兩個欄位經常「一起填/一起空」 | 語意耦合 | 合併或用 nested 結構 |
6. Ticket 六欄位角度解析
ticket 模板的 what / why / when / where / how / acceptance 六欄位是刻意設計的多角度描述系統。每個欄位從不同角度描述同一個 ticket,合起來才是完整規格。
六欄位角度總表
| 欄位 | 角度 | 正確範例摘要 | 不該寫什麼 | 常見混淆模式 | 檢驗標準 |
|---|---|---|---|---|---|
what | 做什麼 | "新增 2FA 設定開關,支援 TOTP 與 email 驗證碼" | 動機、實作、驗收 | 用「因為…所以」開頭,把 why 塞進來 | 讀者能立刻想像成品 |
why | 為什麼做 | "登入崩潰影響 8% 用戶,每週 40 張客服工單" | 動作、實作細節 | 寫「要修 X bug,修改 Y 檔案」,把 what/how 塞進來 | 含業務指標或成本數字 |
when | 何時啟動 / 何時截止 | "v1.2.0 發佈前;依賴 W03-021 完成後啟動" | 產品行為時序 | 寫「當用戶點擊…系統會…」,描述產品行為而非 ticket 啟動條件 | 條件可被程式或人工驗證 |
where | 影響範圍 | layer: Application; files: [src/auth/login_service.py, ...] | 抽象功能名 | 寫「相關的所有地方」,不列具體路徑 | 有具體檔案/模組清單 |
how | 怎麼做 | "加 guard clause → Result 模式 → 補 6 個失敗情境測試" | 驗收條件、動機 | 寫「做完要通過所有測試,成功率 ≥ 99.5%」,把 acceptance 塞進來 | 有步驟、順序、技術選擇 |
acceptance | 怎樣算完成 | "[ ] Staging 連續 24h 成功率 ≥ 99.5%" | 做法、動機 | 寫「體驗變好」「品質提升」等不可量測描述 | 每條都能被勾選(量化或證據性) |
詳細範例(每個欄位各 1 個正確 + 1 個混淆共 12 項):
designing-fields-ticket-6w.md
7. 非 ticket 情境:YAML 配置、API response
核心原則不只適用 ticket。以下兩個情境示範同樣的思維。
7.1 YAML 配置檔案
原則應用:
| 原則 | 在配置檔案的具體形式 |
|---|---|
| 原子化 | 每個配置鍵承載一個決定(超時、重試、快取各自獨立) |
| 索引 | 用 namespace 分組(database.pool.size 而非 dbPoolSize) |
| 意圖顯性 | retryMaxAttempts 優於 retry 或 attempts |
| 可查詢性 | 布林用 isEnabled / hasFallback;時長用 timeoutSeconds 後綴 |
| 欄位設計 | 新增配置前問「這個值會隨環境變嗎?(是→配置;否→常數)」 |
範例(正確):
1database:
2 host: "db.internal"
3 port: 5432
4 pool:
5 minSize: 5
6 maxSize: 50
7 acquireTimeoutSeconds: 10
8
9retry:
10 maxAttempts: 3
11 initialDelayMs: 100
12 backoffMultiplier: 2
13 retryableErrors:
14 - connection_timeout
15 - transient_network_error範例(常見混淆):
1# 錯誤:單位混亂、命名模糊、enum 變自由文字
2db:
3 timeout: 10 # 秒?毫秒?
4 retry: true # 布林還是次數?
5 errors: "timeout,network" # 為何不用列表?
6 mode: "production, maybe" # enum 還是形容詞?混淆分析:
timeout沒標單位 → 應為timeoutSeconds或timeoutMsretry: true語意不清 → 改為retry.isEnabled+retry.maxAttemptserrors用字串列表 → 應為 YAML 陣列以利 parsemode含「maybe」 → enum 必須是有限集合
7.2 API Response Schema
原則應用:
| 原則 | 在 API response 的具體形式 |
|---|---|
| 原子化 | 資料欄位與 meta 欄位分開(資料放 data,狀態放 status) |
| 索引 | 穩定 ID 欄位(id、cursor)讓分頁與查詢可預測 |
| 意圖顯性 | isPaginated、hasMore、totalCount 各司其職 |
| 可查詢性 | 錯誤碼用 enum(errorCode: "INVALID_TOKEN")而非自由文字 |
| 欄位設計 | 新增欄位前問「client 真的會用嗎?還是只是 server 方便?」 |
範例(正確):
1{
2 "status": "success",
3 "data": {
4 "users": [
5 { "id": "usr_001", "email": "alice@example.com", "role": "admin" }
6 ]
7 },
8 "pagination": {
9 "hasMore": true,
10 "nextCursor": "eyJpZCI6InVzcl8wMDEifQ==",
11 "totalCount": 1247
12 },
13 "meta": {
14 "requestId": "req_abc123",
15 "serverTime": "2026-04-16T09:30:00Z"
16 }
17}範例(常見混淆):
1{
2 "ok": 1,
3 "data": {
4 "result": "...",
5 "error": null,
6 "nextPage": "yes"
7 },
8 "info": "取得 10 筆資料,還有更多"
9}混淆分析:
ok: 1數字當布林 → 應為status: "success"enumdata同時放業務資料與錯誤(result+error)→ 原子化違反,應分開nextPage: "yes"字串當布林 → 應為hasMore: trueinfo為自由文字夾帶結構資訊 → 拆成totalCount: 10+hasMore: true
可攜性自檢
本文件可獨立閱讀,不引用任何特定專案的路徑、ticket ID、commit hash 或 hook 系統。核心原則與 ticket 六欄位範例皆為通用概念,可套用於任何採用多欄位結構的文件系統。
速查表
| 想做的事 | 看哪一節 |
|---|---|
| 判斷欄位是否承載太多維度 | 第 1 節「原子化測試」 |
| 設計 ID 格式 | 第 2 節「ID 格式設計」 |
| 讓欄位名自我說明 | 第 3 節「好欄位名 = 好問題」 |
| enum 命名規則 | 第 4 節「enum 命名規則」 |
| 新增欄位前的檢核 | 第 5 節「新增欄位前必問清單」 |
| ticket what vs why 混淆 | 第 6 節「六欄位角度總表」+ designing-fields-ticket-6w.md what/why 章節 |
| 可驗證的 acceptance 寫法 | 第 6 節「六欄位角度總表」+ designing-fields-ticket-6w.md acceptance 章節 |
| YAML 配置命名 | 第 7.1 |
| API response 欄位設計 | 第 7.2 |