上一篇《自動化流程不是設計出來的,是長出來的》收在一句「地圖要粗,驗收要細」,還給了一張一頁的輕地圖:為什麼存在、分幾塊、什麼不能混。
那張輕地圖管的是整個專案的方向。這篇要講再往下一層:當你要做的東西大到一句話交代不完,開工前到底要寫好什麼。工程界給這件事取了一個名字,叫規格驅動開發(Spec-Driven Development,SDD)。
這篇有點長,因為我把範本、句型表、護欄的做法全部放進來了。想直接用的人可以跳到「一份規格範本」那節複製走。
先講什麼時候不用寫
先潑一盆冷水:大部分的事不用寫規格。
我自己的判斷句只有一句:如果它把需求理解錯了,我一句話修得回來嗎? 修得回來的,直接叫它做,做錯再講一句就好。改個檔名、抽一張表、把逐字稿整理成筆記,都是這種。
要寫規格的是另一種:改錯了不容易發現,或發現了也不好改回來的東西。會跑好幾個月的程式、會動到正式資料的、做壞了牽連一片的。
還有一個很實際的理由:整套規格流程很吃 token。每一份文件它都要讀、要對、要更新,一個小需求走完整流程,額度可能比直接做多出好幾倍。規格是留給大事的,不是每件事都要走一遍的儀式。
三級,儀式跟著後果走
我自己的專案裡把這句判斷句寫成三級。分級的用意不是分類學,是讓儀式的輕重跟後果大小綁在一起,不要為了一個十行的 Excel 巨集寫三頁規格。
第零級:一句話修得回來。 不寫規格,直接叫它做。唯一的要求是原本的檢查要跑過、跑綠,不能因為事情小就連檢查都省。這一級的門檻是「錯了容易發現、容易改回來」,不是「改動很小」。兩行的修改,範圍卻很模糊,正是它最會自己發揮的地方,那種要升到第一級。
第一級:修不回來,但範圍很清楚。 寫半頁就好,內容只有驗收條件:在什麼情況下、做了什麼、應該看到什麼。看到的東西要是能觀察的,門檻的數字要寫死,不寫「差不多」「大致上」。審的時候只審這幾行,驗收跑過就收工。
第二級:會動到正式資料、做了收不回來、牽連到別的系統、跟安全有關。 這一級才走完整流程:為什麼要改、規格改了哪幾條、拆成哪幾步,逐行審。做完之後再叫一個沒參與過的 AI 獨立驗一次,而且整個週期只驗這一次,不是每一步都派人。
醫院裡的人對這種分級應該很熟。門診的小處置、住院的常規醫囑、開刀前的同意書,三種文件的厚度本來就不一樣。厚度不是看事情大小,是看做錯了收不收得回來。
為什麼現在值得先寫
這個系列有一句貫穿的話:AI 是人,它操作的是電腦。
它像人,接得住模糊、能判斷;但也像人,慢、要花錢、每次講的都不太一樣。電腦相反:快、不花錢、每次一模一樣,但只做你寫死的那件事。所以模糊的交給它判斷,重複而確定的,叫它寫成程式再交給電腦跑。
而「寫程式」這件事到了它手上,發生了兩個變化,兩個都指向「先寫清楚」:
第一,生成便宜,修補昂貴。 以前寫程式很貴,所以壞了只能修補。現在生成很快,重做常常比修補簡單。當重做變便宜,值錢的就不再是那份程式,是「你到底要什麼」那份說明。說明寫對了,程式可以一直重生。
第二,它會漂,但有錨就拉得回來。 第一步它做到九十五分,每一步都偏一點點,到第十步只剩六十分,而且它自己不覺得。這時候你需要一個東西,讓你能對它說:「回去對規格,這一步對得上第幾條?」規格就是那個錨。沒有錨,你只能憑印象跟它吵。
OpenAI 的 Sean Grove 在 2025 年 AI Engineer World’s Fair 有一場演講叫 The New Code,講了一句我很喜歡的話:會寫規格的人,就是現在的程式設計師。 他還有另一個比喻:我們現在的用法,是跟 AI 講了一大串、拿到程式之後把對話刪掉,只留程式,就像把原始碼碎掉、然後小心翼翼地把編譯出來的執行檔做版本控制。這對醫院裡的人是好消息。交班、醫囑、研究計畫書,我們每天在做的就是把要什麼講清楚。
一份規格裡有什麼:三個必填,三個有才填
我的版本很簡單,六個零件。
必填的三個:
- 背景:只講它不可能知道的。本院的規矩、沒說出口的公平原則、你們單位的慣例。
- 你要的樣子:完成的定義。最省事的寫法是給一個「做對了長這樣」的例子。
- 驗收:怎麼檢查、誰檢查。驗收的方法族和拷問句,上一篇《自動化流程不是設計出來的,是長出來的》已經講過,這裡先知道它是必填。
有才填的三個:
- 界線:硬限制、絕對不要動的東西。
- 素材:現成的檔案、範本、前人做過的版本。
- 這次做到哪:範圍與分期,先做能用的版本。
三格填齊就能開工;後三格有就寫,沒有不硬湊。
前面幾格跟《我怎麼跟 AI agent 講話》裡的四格長得很像,「這次做到哪」就是那篇的三檔。差別在用途:四格是一句話講齊,這六格是一個專案講齊。 一句話交代的事做完就結束;一個專案會活好幾個月,所以多了素材、範圍這些會隨時間變的東西。
示範一個零件:背景
六格裡最容易被漏掉的是第一格,因為它最像廢話。
拿一句很常見的需求來看:「幫我寫一支程式,讀我丟給你的排班 Excel,把每個人這個月的加班時數算出來,輸出格式跟上個月那份一樣。」
要的樣子有了(跟上個月那份一樣),驗收勉強也有(可以拿上個月的結果對)。缺的是背景:本院加班怎麼算?值班算不算?跨日怎麼切?哪些時段依規定不計?這些你天天在用,所以覺得不用講。但它不在你們醫院,它只能猜,而且它猜的時候,跟做對的時候語氣一模一樣。
「只講它不可能知道的」有一個反面:它已經會的不用教。Excel 怎麼讀不用講,SOAP 是什麼不用講。每一句它已經知道的話,都在稀釋它真正需要的那幾句。
含糊一句,對上清楚五句
零件想好了,還要寫成句子。工程界有一套寫法叫 EARS(Easy Approach to Requirements Syntax),2009 年 Rolls-Royce 的 Alistair Mavin 團隊在整理噴射引擎控制系統的適航法規時提出,後來 NASA、Airbus、Siemens 都在用。它的賣點正好是「不用專門工具、幾乎不用訓練」,貼在任何一個 AI 對話框都能用。
想法很單純:每一句需求都長一樣,主幹都是 the system shall〈做什麼〉,差別只在句首那個關鍵字,用來區分「永遠成立」「某件事發生時」「壞事發生時」這些情境。五個句型是這樣:
| 句型 | 寫法(英文原版) | 什麼時候用 | 醫院裡的例子 |
|---|---|---|---|
| Ubiquitous | The system shall <do something> | 永遠成立 | 這張表單應該只接受民國年格式的日期 |
| Event-driven | WHEN <trigger>, the system shall <do something> | 某件事發生時 | 當按下送出,系統應該寄一封確認信給填表人 |
| Unwanted behaviour | IF <unwanted condition>, THEN the system shall <response> | 壞事發生時要保護什麼 | 如果病歷號欄位是空的,那麼系統應該擋下並顯示紅字提示 |
| State-driven | WHILE <state>, the system shall <do something> | 在某個狀態裡才成立 | 離線期間,系統應該先把資料存在本機 |
| Optional feature | WHERE <feature is included>, the system shall <do something> | 有那個功能才適用 | 在有健保卡讀卡機的電腦上,系統應該自動帶入基本資料 |
這五個關鍵字我直接留英文,不翻。它們是整套寫法的骨架,一翻成中文每個人翻的都不一樣,反而看不出兩句是同一種句型;而且你把規格丟給 AI 的時候,shall、WHEN、IF…THEN、WHILE、WHERE 它一眼就認得。句子的內容照樣用中文寫,只有句首那個關鍵字留英文,下面的例子都是這樣寫的。
拿我自己的一個小需求示範
平常我會這樣講:
幫我做一個追新論文的東西。
寫成規格是五句,剛好一個句型一句:
- 系統 shall 每天早上七點,列出前一天新上架的復健論文。
- WHEN 清單做好,系統 shall 寄到我的信箱。
- IF 那天一篇都沒有,THEN 系統 shall 寄一封「今天沒有」。
- WHILE 斷線期間,系統 shall 一小時後再試一次。
- WHERE 有全文,系統 shall 附上連結。
含糊那一句其實只講了第一句的一半。何時跑、寄去哪、沒資料怎麼辦、斷線怎麼辦、有全文要不要附,四句半是寫規格的時候被句型逼出來的。
其中最有價值的是第三句。這一句我含糊講的時候絕對不會想到。它是「壞事發生時要保護什麼」的句型,逼你去想沒資料、斷線、查無全文這些平常不會主動想的狀況。醫院裡的人對這種句型應該很熟:「如果病歷號空白,那麼擋下提示」,跟臨床路徑的分支寫法是同一件事。
第四句也值得多看一眼。「斷線期間」是狀態,不是事件。沒有這句,它很可能寫成「斷線就放棄」,然後那天的清單就沒了,你也不會知道。
講不出來怎麼辦
五句寫不出來也沒關係,有三招,三招都是把「寫」變成「講」。叫它拷問你,「先問我,一次一題,問到你清楚為止」,答完叫它把答案整理成五句;流程不知道該長怎樣,叫它「一次給我三種,我選」,三種擺在一起你會突然知道自己不要哪兩種;或者邊做邊錄音,把平常怎麼做這件事講一遍,逐字稿整包丟給它抽成規格。拷問那招的完整問法和實際問出什麼,在排班那個例子裡寫過了。
一份規格範本
上面講的六個零件加五個句型,合起來就是一份規格。這是我自己用的骨架,段落名就是六個零件,每段下面一行提示。複製走,把提示換成你的內容就能用:
# spec — <這個東西叫什麼>
## 背景
只寫它不可能知道的:本院怎麼算、誰有權限、沒說出口的慣例。
它已經會的(Excel 怎麼讀、SOAP 是什麼)不要寫。
## 你要的樣子
完成的定義。最省事的寫法:附一個「做對了長這樣」的例子檔。
用五個句型寫需求,一句一條:
- 系統 shall …
- WHEN …,系統 shall …
- IF …,THEN 系統 shall …
- WHILE …,系統 shall …
- WHERE …,系統 shall …
## 驗收
怎麼檢查、誰檢查。每條寫成「給定/當/那麼」,「那麼」要是看得到的東西,門檻寫數字:
- 給定 上個月的排班檔
當 程式跑完
那麼 每個人的加班時數跟上個月人工算的那份一致,誤差 0 小時
- 給定 一個病歷號空白的列
當 程式跑到那列
那麼 程式停下並列出那一列,不能自己補值
## 界線(有才填)
絕對不要動的東西。例:不能改原始檔、不能連外網、薪資欄位不能出現在輸出。
## 素材(有才填)
現成的檔案、範本、前人做過的版本。放路徑,不要貼內容。
## 這次做到哪(有才填)
範圍與分期。第一版做到哪裡就能用;哪些明講「這次不做」。
幾個寫的時候的提醒:
- 「那麼」要看得到。 「那麼系統應該正確計算」不是驗收,「誤差 0 小時」才是。門檻寫成數字,它就沒有空間跟你討價還價。
- 「這次不做」要明寫。 這一格的用處是擋住它的熱心。你不寫,它就會順手幫你做,然後你要花時間把多做的拆掉。
- 驗收的每一條都要能重跑。 第一次拿上個月的檔案對過,下次改完程式還要能再對一次。這件事下面會再講。
規格寫完,先產生驗收,才動功能
順序上有一件事很容易做反:規格寫完,不要馬上叫它寫功能。先叫它把驗收那格變成一份跑得起來的檢查。
我的講法就一句:「照規格的驗收段,先寫成一支檢查程式,功能先不要動。」寫完先跑一次,這時候整片是紅的,功能還沒做,本來就該紅。紅的那一刻其實在幫你確認兩件事:這支檢查真的跑得起來,而且它真的在驗你要的那件事。一支永遠綠的檢查,跟沒有檢查是一樣的。
先有驗收還有一個更實際的好處:它自己知道什麼時候算做完了。 沒有驗收的時候,「做完了」是它說了算,你只能自己開來看;有驗收的時候,做完的定義就是那幾條跑綠,它會自己跑到綠為止,中間錯了自己修。
工程界管這叫測試先行,不是新東西。差別在以前寫測試很貴,所以大部分人都跳過;現在它幾秒鐘就寫好了,跳過的理由沒了。
改需求,先改規格,再改程式
規格寫好之後,最常見的死法是:它變成一份「開工那天的規格」。
需求一定會改。改的時候如果直接叫它改程式,規格三天後就跟實物對不上,錨就掉了。所以順序要反過來:先改規格,再叫它照規格改程式。 事後才回頭補寫的規格,只是一份 changelog。
這條不能靠記得。我把它寫進專案的規則檔,它每次開工都會看到。講話那篇給過規則檔的起手模板,「固定做法」那段就這一行:
## 固定做法
- 改需求先改 spec/,再改程式。做到一半發現需求變了,回去先改 spec 再繼續。
把這條擋成硬規則
規則檔是「請你記得」。「請你記得」會失效,人會忘,它也會。最貴的那幾條紅線,我的做法是寫成程式,讓它想錯也錯不了。
具體是這樣:每次它要送出一次版本(git commit)之前,有一支小程式先看一眼這次改了哪些檔案。邏輯只有三條:
- 改到的檔案裡有程式碼嗎?沒有,放行。
- 有程式碼,那同一次有沒有也改到規格,或至少在變更紀錄加了一條?有,放行。
- 都沒有,擋下來,把改到的程式檔列出來,問它三選一:這次改的是使用者看得到的行為(回去改規格)、是修 bug(補一條變更紀錄)、還是純內部整理(在訊息裡加
[no-spec]這個暗號重送)。
示意寫成程式大概長這樣,實際那支長一些,但邏輯就是這幾行:
CODE = ("converter/", "figures/", "skills/") # 動到這些算「改行為」
SPEC = ("openspec/", "CHANGELOG.md") # 動到這些算「有交代」
staged = git_staged_files()
touched_code = any(p.startswith(CODE) for p in staged)
touched_spec = any(p.startswith(SPEC) for p in staged)
if "[no-spec]" in commit_message:
allow() # 逃生口:純內部整理
elif touched_code and not touched_spec:
block("改了程式卻沒改規格,先回去改 spec/ 或補 CHANGELOG")
else:
allow()
這裡有一條設計原則我覺得比程式本身重要:護欄自己壞了,不能反過來擋人。 找不到 git、讀不到檔案、任何它自己搞不清楚的狀況,一律放行。一支會因為自己的問題擋住你工作的護欄,比沒有護欄更糟。
還有一個坑是我自己踩到的:這支程式一開始只掛在 AI 的工具上,結果從別的資料夾開的工作階段根本不會讀到它。程式邏輯全對,測試全過,就是沒接上。後來多掛了一份在 git 本身的鉤子上,不管是 AI、別的工具、還是我自己手打指令,都得經過同一支檢查。護欄要問的三句話:誰讀它、什麼觸發它、壞了你會知道嗎。
有了規格之後,還要留下什麼
規格是開工前寫的。開工之後,我的專案裡固定會累積兩樣東西,兩樣都是規格的延伸。
存下來重跑的檢查
前面那支開工前就先寫好的檢查,做完之後不要丟掉。下次改完程式,那支檢查再跑一次,幾秒鐘就知道有沒有改壞。有些驗收第一次得人手對,拿上個月的檔案跑一遍,看數字合不合,對過之後就叫它把這次對的過程也存成一支檢查程式,一樣留著。
我有一個把教科書 PDF 轉成筆記的工具,累積到現在六支這種檢查。每一支都是一次真的出過的錯:表格跨頁被切成兩半、橫著印的表格轉出來整個反過來、圖抓對了位置卻不是一張圖。出錯那天先手動找出來是哪裡壞,修好,然後把「這本書的這一頁應該轉出這樣」存成檢查,永遠留著。
規則檔裡跟這件事綁在一起的有一條紅線:不准為了讓失敗的案例過,去調檢查的門檻。 這條寫進去是因為它真的會這樣做。你叫它修一個抓表格的問題,它發現改門檻最快,門檻一放寬,失敗的案例過了,測試全綠,然後三本書之後你發現另外兩本的表格也一起壞了。門檻要動,只能是量過一批書之後動,不能因為某一本過不了而動。
每條變更紀錄是一個失敗模式
第二樣是變更紀錄(CHANGELOG)。我的寫法跟一般工程師不太一樣:每一個版本的標題不是「新增什麼功能」,是這次抓到的失敗模式是什麼。
同一個工具的紀錄,最近幾條的標題長這樣:
- 一個安靜的錯答案,比拒答更糟
- 靠模型判斷的檢查,不能是負責擋下的那個
- 通過品管的裁圖,還是得是一張圖
- 橫著印的表格不再倒著出來
- 章節引用要查,不能信
每一條都是先在真實的書上量到、再修、再存成檢查。這樣寫的好處是:一年後回頭看,你看到的不是功能清單,是這個工具在哪些地方曾經騙過你。這份清單跟規格是同一件事的兩面。規格寫「應該怎樣」,紀錄寫「曾經不怎樣」。
而且這個工具的規格,其實是事後才從說明文件和變更紀錄裡抽出來的。前面說「事後補的規格只是 changelog」,這裡看起來自相矛盾,其實不是。那是還債:先有了一堆散在各處的規矩,才回頭把它們收成一份可以對照的規格,從那天起改需求就先改它。上一篇說的該重蓋的訊號到了、還債三步的第一步,就是這個。
有規格當錨、有可重跑的驗收,修 bug 就不會改到面目全非。真的改到面目全非,那是重蓋的時候到了,不是規格沒用。
錯的規格,錯得很有權威
最後一件事,也是我覺得最需要先講的。
它不會質疑你的規格。規格寫錯了,它就一路錯下去,而且做得很整齊、很有信心。規格被核准之後,錯誤只是變得更有權威而已。
所以「做對了東西嗎」可以交給驗收,「做了對的東西嗎」永遠是你的。這兩問的差別和驗收的三個層次,上一篇講過,這裡不重複。「你要的樣子」那一格,是整份規格裡唯一不能外包的。
有了規格之後能做什麼
規格是這個時代的程式碼。程式壞了、模型換了、工具停更了,把規格拿給一個新的 AI 跑一下,整個系統就活回來。你手上真正值錢的資產不再是那堆程式,是那份「我到底要什麼」。
而寫那份東西的人,就是程式設計師。
