跳到內容

選擇語言

目前語言: 繁體中文

編寫良好的規格說明

你很少會從空白頁開始編寫規格說明。你先用通俗語言描述變更,/opsx:propose 起草需求和情境,然後由你把它們打磨好。本頁關注最後這一步——什麼樣的規格說明才算“好”,以及如何引導 AI 寫出這樣的內容。

本文與審查變更相輔相成:審查是發現草稿中的薄弱之處,編寫則是瞭解優質規格說明由什麼構成。

規格說明描述行為,而非程式碼

Section titled “規格說明描述行為,而非程式碼”

規格說明應描述系統做什麼,並且任何人都能檢查,而不是描述系統如何建置。它由需求(對行為的陳述)和情境(證明需求成立的具體範例)組成。

### Requirement: Session Timeout
The system SHALL expire a session after 30 minutes of inactivity.
#### Scenario: Idle timeout
- GIVEN an authenticated session
- WHEN 30 minutes pass with no activity
- THEN the session is invalidated and the user must re-authenticate

將實作方式——佇列、庫、表結構——放在 design.md 或程式碼中。如果把行為和實作混在同一條需求裡,這條需求就無法測試,並且程式碼一旦變化,它很快就會過時。

一條優質需求只描述一種行為,並且表述清楚到可以交給別人測試。

  • 一句話,一個 SHALL/MUST。 如果一條需求包含三項“還要”內容,那實際是三條需求。應將它們拆開。

  • 可觀察。 不看程式碼的人也應該能夠判斷需求是否滿足。“上傳內容超過 10 MB 時,系統 SHALL 顯示錯誤橫幅”是可觀察的;“系統 SHALL 妥善處理大型上傳”則不是。

  • 嚴格程度恰當。 OpenSpec 使用 RFC 2119 關鍵詞,各關鍵詞含義不同:

    關鍵詞 含義
    MUST / SHALL 強制要求,不容協商。
    SHOULD 強烈建議,但有理由時允許例外。
    MAY 確實可選。

    預設使用 MUST/SHALL。只有在確實表示“除非有充分理由不這樣做”時才使用 SHOULD。

檢驗一條需求的方法是:一個從未看過程式碼的測試人員能否判斷它是否透過? 如果不能,就需要把需求寫得更明確。

情境能體現需求的價值。每個情境都是一個具體的 GIVEN / WHEN / THEN,並且可以轉化為自動化測試。

  • 情境要驗證對應需求。 只換一種說法重述需求的情境毫無測試價值。應描述一種具體情形及其具體結果。
  • 覆蓋重要情形,而不只是正常流程。 成功登入很容易想到。空輸入、過期令牌、再次點選、出錯情形——這些才是 bug 常見之處,也是情境最有價值的地方。
  • 在標題中說明所測情形。 “Scenario: Rejects an expired token”能讓審查者一眼看出測試覆蓋範圍;“Scenario: Test 2”則不能。

一個實用習慣:批准之前,先問自己最不希望看到哪種情形出錯?——並確保有一個情境明確描述了它。

變更透過三種章節型別描述對規格說明的修改。正確選擇可確保歸檔後的規格說明如實反映實際情況:

  • ## ADDED Requirements——新增之前不存在的行為。
  • ## MODIFIED Requirements——修改已有行為。應寫出完整的新版本;簡要說明變化內容有助於審查者理解。
  • ## REMOVED Requirements——刪除即將廢止的行為,並說明原因。

歸檔時,ADDED 內容會追加到主規格說明中,MODIFIED 內容會替換舊版本,REMOVED 內容則會從主規格說明中移除。刪除某項能力的最後一條需求,就表示要廢棄該能力:歸檔會刪除 openspec/specs/<capability>/spec.md,而不是留下空檔案。因為這是歸檔過程中唯一會刪除檔案的步驟,所以必須明確請求:在變更的 .openspec.yaml 中新增 retire_capabilities: true,並保留該檔案已有的 schema: 設定。未設定此項時,歸檔會中止並說明原因。

廢棄操作會刪除整個檔案;因此,如果規格說明中除了標題、## Purpose 和需求塊之外還包含其他內容(例如 ## Notes 章節或需求下方的註釋),操作也會被拒絕。中止訊息會指出這些行;你可以將其移至 ## Purpose 或某項需求中,也可以手動刪除規格說明。如果規格說明位於呼叫者當前檢出的儲存庫中,歸檔輸出還會說明如何使用 git checkout 恢復已提交的檔案;如果是所選儲存庫,則會提供針對該檢出範圍的恢復說明。

如果把一項真實變更標為 ADDED,就會留下兩條互相競爭的需求;如果把新行為描述為 MODIFIED,則沒有舊內容可供替換。拿不準時,先開啟當前規格說明,確認該需求是否已經存在。

還有一種章節值得了解。如果差異建立了一個此前不存在的能力,應使用 ## Purpose 開頭,並用一兩句話說明該能力的用途。歸檔時,這段內容會成為新建主規格說明的 Purpose;如果省略,則會留下 TBD 佔位符,需要手動補充。現有規格說明已經有 Purpose,因此其中差異檔案寫的 Purpose 會被忽略——如需修改它,請直接編輯 openspec/specs/<capability-path>/spec.md。這裡的 <capability-path> 是相對於 specs/ 的目錄,例如扁平專案中的 user-auth,或按領域組織的專案中的 identity/user-auth。

最常見的編寫錯誤並不是需求措辭不佳,而是試圖在一個變更中塞進三項變更。

優質變更只有一個意圖,可以用一句話說清楚。 “新增深色模式切換開關。”“對登入端點限流。”“將會話儲存從 cookie 遷出。”如果描述變更時需要反覆說“還要……”,就說明應該拆分。

以下跡象表明變更過大:

  • 提案中的範圍像一份互不相關功能的清單。
  • 審查要花一個下午,因此沒人會認真審查。
  • 兩個人無法同時處理而不相互衝突。
  • 一半任務可以單獨釋出。

更小的變更更易審查、更容易在一次專注的工作時段內完成,也更便於在六個月後僅剩歸檔時理解。你可以並行執行多個變更——參閱編輯與迭代和工作流程。

反過來也一樣:修復一處拼寫錯誤,不需要列三項需求並撰寫設計文件。流程的複雜程度應與風險相匹配。

/opsx:propose 會起草初稿,因此產出的品質取決於你提供的資訊。你不必手動編寫需求,但需要為 AI 指明正確方向:

  • 說明意圖和邊界。 “新增深色模式切換開關,首次載入時遵循作業系統設定——不要修改現有主題 API。” 範圍外內容與範圍內內容同樣重要。
  • 指出你關心的情形。 “確保包含一個使用者已手動選擇主題的情境。” AI 會重點覆蓋你指出的內容。
  • 然後進行編輯。 規格說明是純 Markdown。你可以明確含糊的 SHALL、刪除毫無測試價值的情境、補上遺漏的情況,也可以請 AI 幫忙:“超時需求太含糊了,請明確為 30 分鐘。”

起草、完善、重複。經過幾輪這樣的迭代,你就能得到一份值得信賴的規格說明——這正是整個流程的目標。

  • 每條需求都是一種可觀察的行為,並包含 SHALL/MUST。
  • 需求中沒有混入實作細節。
  • 每條需求至少有一個真正驗證其行為的情境。
  • 重要的邊界和錯誤情形都有對應情境,而不僅是正常流程。
  • 根據當前規格說明,正確使用 ADDED / MODIFIED / REMOVED 差異型別。
  • 整個變更只有一個意圖,並且可以用一句話描述。
  • 審查變更——用兩分鐘檢查找出遺漏問題。
  • 概念——深入瞭解規格說明、變更和差異背後的模型。
  • 範例與配方——從頭到尾檢視真實變更。

HagiCode

HagiCode 是智慧代理程式開發工作台,結合結構化工作流程、多代理程式執行與 Hero Dungeon 介面,將想法化為交付成果。

以更聰明、更快速且更有趣的智慧代理程式工作流程,打造實用的軟體。

HagiCode 淺色主題介面畫面
  • Smart結構化流程將意圖轉化為從構想到交付的可執行步驟。
  • Efficient多代理程式工作流程讓研究、實作與審查並行進行。
  • FunHero Dungeon 讓長時間的程式協作更直覺、更有參與感。
造訪 HagiCode