
前幾天,我們一直在問開工前的問題:怎樣才算完成?哪些規則不能忽略?我認為該改的地方,真的找對了嗎?PM 交來的需求,還缺哪些決定?
讀到這裡,可能會想:所以,什麼時候才開始寫程式?(敲碗
前面連著幾篇都在談分析,是因為我越用 AI,越覺得這段不能省。以前寫程式要花時間,還有機會邊寫邊發現需求沒想清楚;現在 Claude Code 很快就能交出實作,沒釐清的假設也可能跟著變成程式。
AI 把寫程式變快了,也讓「到底要寫什麼」變得更重要。 分析不是多寫幾份文件,而是把目的、規則、例外與未決事項攤開,讓規格有依據,讓後面的開發少靠猜。前面花時間做的準備,今天就要拿來用了。
昨天那份訂單取消的需求交接摘要,就是接下來的起點。也就是前篇的「開工卡」:把範圍、規則與未決事項交清楚,讓 RD 接著做設計。範圍比較清楚了,但 RD 還得回答:退款規則放哪裡?哪些地方會收到這個結果?改完要怎麼驗?
Anthropic 的 AI-native SDLC playbook 有一個我想借用的做法:每個階段留下下一步能接著使用的產物。 它把工作分成 Plan、Design、Build、Test、Deploy、Maintain,但這些階段會來回修正,不是走過一次就結束。
前面談過的目的、理由、查證與需求交接摘要,到了這裡,都要變成設計的依據:
| 開發時要回答的問題 | 前面怎麼處理 | 留下的文件或紀錄 | 接下來怎麼用 |
|---|---|---|---|
| 為什麼做、怎樣算完成? | Day 6:目的與驗收條件 | intent.md、spec.md |
設計與測試不能偏離目的 |
| 有哪些不能忽略的背景? | Day 7:理由卡與專案規則 | CLAUDE.md、相關決策文件 |
分析與實作時讀取適用規則 |
| 我認為要改的位置對嗎? | Day 8:追路徑、找反證 | 程式位置與查證紀錄 | 避免從錯誤假設開始修改 |
| 這次改什麼,還缺哪些決定? | Day 9:需求交接摘要 | 規格、未決事項與交接摘要 | 確定設計範圍與待確認條件 |
| 準備怎麼改、怎麼驗? | Day 10:設計與實作計畫 | 設計決定、圖與 plan.md |
交給後面的實作、測試與審查 |
這是本系列的工作與文件對照,不是官方規定的一套檔名。需求交接摘要可以直接留在 Jira 或規格裡,不必再多維護一份同樣內容。
前幾篇用了不同案例,練的是不同的判斷方法;接下來,則沿著 Day 9 的同一份訂單取消程式,把設計、實作、測試與交付走下去。承接的是方法,不把前面不同案例的實跑拼成同一張票。
用我熟悉的分工來說,前面偏系統分析(SA):釐清問題、規則與完成條件。今天往系統設計(SD)走:決定元件責任、資料與介面,以及失敗時怎麼處理。這不是單向交棒,設計發現需求有缺口,仍要回去確認。
功能需求告訴我系統要做什麼,非功能性需求則限制它必須在什麼條件下做到。 延遲、容量、可靠性、安全與可觀測性不能等程式寫完才想;它們應在分析時提出,在設計時變成取捨與驗法。AI 能加快提出方案,不會替我們決定系統可以慢多久、資料可以丟多少。
Day 9 還有待確認的問題,不能到了今天就當成全部有人回答。本篇用教學規格 rules-v2.md 固定一個條件:已付款、未出貨的訂單,第一次取消時退款旗標為 true;重複取消則為 false。這裡的旗標表示本次取消的結果,不是訂單退款歷史,也不會真的呼叫金流。
只讀條列規格,容易漏掉「已付款,但早就取消過」這種交叉條件。我會先把規則畫成流程圖,沿每條分支走一次:先看已出貨,再看是否重複取消,最後才看付款狀態。圖呈現的是預期結果,不規定程式只能照這個分支順序寫。

流程圖回答「遇到這個條件,應得到什麼結果」。它是 rules-v2 的閱讀輔助,不代表舊程式已經做到。
這是示範採用的規則,不是公司 Owner 的正式核准。真實專案可以先比較設計,但未確認的規則仍得列在待決事項,不能直接當成上線依據。
昨天把需求文件與程式分組給 Claude,看它各自能查到什麼;今天把規格、需求交接摘要、Domain、API 放在一起,再追到教學用的通知接收端 FakeSink,看看送出的內容是否真的留下可核對的紀錄。仍只提供 Read、Grep、Glob 工具,請它追資料流、比較規則落點、草擬計畫與循序圖,不修改功能。
第一輪十二個回合、一百一十秒,它交回一份約四千字的設計審查包。我接下來要做的,是把這些分析收成開發能照著走的計畫。
先看這些元件的位置,再追退款旗標經過的邊界:

元件位置與結果流向的閱讀示意,不表示執行先後。Domain 與 Worker 都在同一 API 程序;HTTP 與日誌是輸出,不是另外兩個服務。虛線註記的正式授權尚未完成。
Claude 整理出的資料流,讓「只改一個欄位」具體了起來:
| 位置 | 做什麼 | 固定快照中的依據 |
|---|---|---|
| Domain | 算出 result.RefundRequested |
Cancellation.cs:13、:21 |
| API | 把結果放進 HTTP 回應、日誌、通知物件 | Program.cs:89、:88、:79 |
| 通知發送端 | 把旗標序列化進 JSON body,送給 FakeSink | Program.cs:200 同步、:231 非同步 |
| FakeSink | 保存通知識別資料,但沒有保存退款旗標 | FakeSink/Program.cs:17-26 |
FakeSink 有收據,不代表收據包含通知的所有內容。
這張表讓後面的驗證有了位置:除了 Domain 算得對,還要看 HTTP、日誌與送出的 payload 是否帶著同一個結果。只看單元測試,還看不到這些邊界。
沿用 Day 6 的做法,先確認這次要完成的行為與限制:提出退款要求,但不執行退款。接下來才問:在 API 改,還是在 Domain 改?Claude 建議留在 Domain。我把兩個選項放在一起看:
| 做法 | 對這份程式的影響 | 這次的選擇 |
|---|---|---|
在 Domain 的 Cancel 計算退款旗標 |
API 繼續轉發同一份結果,Domain 測試能直接核對規則 | 採用 |
API 收到結果後,另外依 Paid 重算 |
HTTP、日誌與通知可能拿到不同版本的判斷;Domain 測試也管不到新增的 API 規則 | 不採用 |
這不是因為「業務邏輯就該放 Domain」這句口號。核對目前程式,退款旗標確實由 Cancel 產生,API 只是把它接出去。API 仍有出貨狀態對應 HTTP 狀態碼的條件,不能因此說它完全沒有判斷。
所以這次的設計決定很小:退款規則留在 Domain,公開介面不變;測試則分別確認規則本身與 API 轉發結果。 不需要為了改一個旗標,把整個 API 重寫。
把退款旗標算對,只回答功能的一部分。沿著同一條取消路徑,還會遇到這些問題:下面是依既有程式整理的設計盤點,不是新增壓測或故障實驗。
| 設計面向 | 放到這個案例,要問什麼? | 目前能說到哪裡/下一步怎麼驗 |
|---|---|---|
| 相容性 | 布林型別沒變,其他呼叫端是否仍把它當退款歷史? | 已追到 HTTP、日誌與通知;外部使用者未知,要補契約與消費端核對 |
| 可靠性與並行 | 兩次取消同時到,或入列後程序重啟,會怎樣? | 讀、判斷、寫回非整體原子;儲存與佇列在記憶體。並行與重啟需另驗,不能保證不重複或不遺失 |
| 可觀測性 | 有收據,能否證明送出的退款旗標正確? | FakeSink 未保留該欄位;測試接收端需留下 payload,才能核對內容 |
這些問題不代表本輪都要修。它們讓設計能交代:哪些沿用、哪些本次處理、哪些還不能承諾。缺少目標就留下待決事項,不請 Claude 自行填一個漂亮的 p95 或可用率。
資料流查得出來,結論仍可能跨太遠。Claude 在第一輪寫道:
本輪修改 BR-03 的可觀察外部影響實際只有兩個:API JSON 回應、logs.jsonl 這一行紀錄。通知送達與否不受影響。
這段要拆開看。FakeSink 沒保存退款旗標,是程式能核對的事;發送端的 payload 仍包含它,規則改了,送出去的內容也可能改變。因此,不能只因接收端沒記錄,就把通知內容排除在驗證之外。
但反過來也一樣:payload 改變,不代表通知會送不到。 這輪只讀程式,沒有執行通知送達驗證,不能把兩件事混成同一個結論。
我把它改成計畫裡的一個具體要求:用會保留 payload 的測試接收端,核對 refund_requested,不能只拿「收到一張收據」當成內容正確。
相同輸入與提示共跑三次,資料流都追到相同位置,影響範圍的說法卻有寬有窄。這些紀錄足以提醒我回查推論,不能拿來估算模型錯誤率。
它畫出的首次取消循序圖,把 API 回 200 放在通知入列之前。正常首次取消的非同步分支,程式卻是先 await 入列,再寫日誌、回應。
程式實際的順序是這樣(節錄 src/Api/Program.cs,省略號為略過的欄位):
if (transitioned)
{
var n = new Notification(..., result.RefundRequested);
metrics.Inc("notify_enqueued_total");
await channel.Writer.WriteAsync(n); // ← 第 84 行:先等入列完成
}
log.Write(new { ..., queue_depth = channel.Reader.Count });
return Results.Json(new { ..., refund_requested = result.RefundRequested },
statusCode: status); // ← 第 89 行:才回應
看那兩行註解:await 在回應之前。原圖把兩者對調,一個箭頭的位置就改變了 API 對呼叫端的承諾。
前面的流程圖查業務分支,這裡的循序圖查元件交接:API 呼叫 Domain 得到結果,更新狀態,再通知與回應。兩張圖一起看,才能分清「結果應該是什麼」與「現在怎麼把結果送出去」。
這會影響讀圖的人怎麼理解 API 的承諾。我另外開一個乾淨的唯讀 session,只給 API、Domain 與那張圖,請 Claude 逐箭頭核對程式,附行號並分清「順序畫錯」與「省略分支」。

只看正常首次取消的非同步分支:這張圖將原始循序圖簡化為順序對照,左邊保留原圖錯誤,右邊依程式重排。入列在 HTTP 回應之前,通知送達與回應則沒有固定先後。
三次設計輸出中,兩次畫反、一次正確;另做的三次逐箭頭核對都找到了錯誤。對圖時提供的檔案也減少了,不能把結果全歸因於換了提示,更不能保證另開 session 就一定查得出來。
對這份設計,我留下兩個重點:正常首次取消要先完成入列再回應;入列不等於送達,更不等於已經可靠保存。 重複取消也可能回 200,卻不新增通知,不能把所有 200 都解釋成「剛放了一筆通知」。
圖可以簡化,但得標明畫的是哪個分支。這次保留圖,也把核對後的順序與限制一起帶進計畫。
把查到的問題直接排成開發步驟,還是可能漏東西。收計畫之前,我先把這次修改往外展開一次:

每一枝只要標「本次處理」「沿用既有」「待確認」或「不適用及理由」,不是要求一個小修改把整套系統重做一次。
這一輪真的問出兩件原本不在步驟裡的事:正式授權未完成,不妨礙在本機驗證退款規則,但會擋住把教學 API 當成正式服務上線;程式退版也撤不回已經送出的通知,回復方式得另外想。兩項都進了計畫的「這輪沒解的事」。
圖的用處是讓分歧有位置可談。同一句「取消後發通知」,有人理解成放進佇列就完成,有人以為要等對方收到—指著同一條箭頭問「這裡要等嗎、失敗誰接住」,比再讀一次文字快。Claude Code 可以先讀規格與程式起草,人再拿圖對規則與呼叫順序。
但Mermaid 方便保存與比較,不保證內容正確:本篇那張原始圖就是 Mermaid,順序照樣畫反。先核對依據,再用圖協作。
plan.md 要寫到 RD 知道改哪裡、怎麼驗討論完不能只留「加強安全性」「補測試」。我會把四千字分析拆成下面這種工作:指定修改位置、說清楚動作,再寫下如何判斷做完。這是待執行的計畫,今天的唯讀分析沒有完成這些測試。
每項工作都用同一條線寫清楚:依據 → 修改位置 → 動作 → 驗收方式 → 證據位置 → 待決與停止條件。 圖與設計理由可以引用原文件,不必整份複製進 plan.md;證據位置則先指定,執行後才填結果,不能預先寫通過。
| 工作 | RD/Claude Code 接下來做什麼 | 怎麼驗收 |
|---|---|---|
| 先確認規則差異 | 在 tests/DomainTests/Program.cs 依 rules-v2.md 的 SC-01~SC-07 調整預期 |
舊實作在哪些情境失敗,能對回這次改變的規則;不為了綠燈任意改預期 |
| 修改退款判斷 | 改 src/Domain/Cancellation.cs,API 繼續轉發結果,不重算、不接金流 |
七個規格情境通過,再確認公開介面沒有改動 |
| 查結果有沒有一路傳對 | 補 API 邊界測試,讓測試接收端保留必要的通知欄位 | HTTP、日誌與通知的 refund_requested 對得上;依序重送不新增通知 |
授權也寫成工作,不寫成一句「加強安全性」。這份教學 API 目前只讀 X-Actor、有值就繼續;那證明請求帶了欄位,不證明身分,更不證明他有權取消這張訂單。退款算得對,也不能讓任何人都取消別人的訂單。 計畫裡因此多一列:改用已驗證的使用者身分、補拒絕路徑的測試、定義日誌可記哪些欄位—本輪都未實作,列進「這輪沒解的事」,不當成已完成。
同樣地,「依序取消兩次」不等於「同時取消兩次」。目前讀取、判斷、寫回不是整體原子操作,儲存與佇列又在記憶體裡;並行、重啟與外部相容性仍是待驗事項。教學功能可以先完成,但不能據此說正式上線條件都齊了。
先用本篇的分析包練習,再換成自己的專案。配套目錄為 days/day10/lab-design;目前已整理在本機配套 repo,尚未推送,遠端讀者入口待公開後提供。這是固定快照的閱讀分析包,不含完整 .NET 建置環境。
取得分析包後,在 PowerShell 切到該目錄,先核對既有原件,再選擇是否呼叫 Claude:
cd days/day10/lab-design
python verify.py
# 需先安裝並登入 Claude Code;每次使用新的 run 名稱
python run.py my-design-01 reader
第一行檢查不呼叫模型;看 hashes_match 與 analysis_completed 是否為 true,而 dotnet_executed 應為 false。這只核對固定輸入及第一次分析紀錄,不代表設計正確。第二個指令才會消耗模型用量;runner 把回覆存到 runs/my-design-01/result.md,另留 trace.jsonl、meta.json 與 stderr.txt。名稱若已存在,改用 my-design-02,不要覆蓋舊結果。
先準備指定版本的需求、相關實作與測試,讓 Claude 做設計分析。兩次操作都限制為 Read、Grep、Glob,不只在提示裡說「不要改」。下面是依本次整理補強的設計提示,包含非功能性檢查,尚未用它新增實跑。前述 runner 的 reader 仍使用歷史提示,兩者不能當成同一輪。要套用新提示,可存成 design-request.txt,沿用下方唯讀 CLI 呼叫方式、輸出另存 design-result.md:
以指定版本的需求、決策文件、既有程式與測試為依據,只讀不改檔。
先依目的範圍、設計協作、開發驗證、安全品質、發布維運、未決責任六個分支查漏。
各項標本次處理、引用既有做法、待確認或不適用及理由,附依據。
列已確認與未決的功能需求、品質目標及限制,缺數值就提問,不代填。
追蹤誰算、誰讀、誰傳、誰保存本次修改的值,附檔案位置。
比較至少兩個合理的設計選項與代價;不必要的方案說明為何排除。
檢查相容性、效能、可靠性、安全與可觀測性:
每項列依據、未知、需由誰決定,以及準備怎麼驗。
安全檢查需指出:身分從哪裡驗證、在哪裡檢查訂單權限、哪些欄位能記日誌。
列出合法與越權請求的測試,拒絕時還要驗證狀態未變、沒有新增通知;未知權限不代填。
用 Mermaid 表達必要的 C4 架構視圖、流程與循序關係,分清現況與預期;
C4 先選層次,不把同一程序內的元件畫成獨立服務;三種圖對齊相同範圍與版本。
節點與箭頭對回規格編號或程式位置,不為湊三張圖補不存在的關係。
最後輸出供團隊討論的設計說明與 plan.md 草稿:
每項寫依據、修改位置、動作、驗收方式、證據位置、待決與停止條件。
未決事項列待確認的負責角色,說明阻擋哪一步;不要虛構已有人接受。
分開本輪功能修改與正式上線前的待辦,不把安全提案或未跑測試寫成已完成。
若要用上面的補強提示,存檔後執行:
Get-Content -Raw design-request.txt | claude -p --tools Read,Grep,Glob --allowedTools Read,Grep,Glob > design-result.md
這次看 design-result.md;沿用歷史 runner 的讀者則看 runs/my-design-01/result.md,不要把兩份回覆混為同一次執行。
拿到圖後,把回覆中的 Mermaid 原始碼複製到 diagram-to-check.md,不要先修掉可疑箭頭。這個檔案由你保存;唯讀 Claude 不會替你建立它。
接著把下面的提示存成 check-my-diagram.txt。其中先指名 specs/rules-v2.md、src/Api/Program.cs、src/Domain/Cancellation.cs 與 diagram-to-check.md,讓第二次操作核對同一份快照:
讀取 specs/rules-v2.md、src/Api/Program.cs、src/Domain/Cancellation.cs、diagram-to-check.md。
不修改檔案。逐箭頭核對這張圖與提供的程式,附行號。
若圖是現況,對回程式;若圖是預期行為,對回指定規格,勿混成同一標準。
特別查 await、條件分支、入列、HTTP 回應與背景送達的先後。
核對品質目標是否有來源與驗證方法;未執行的測試不得寫成通過。
分開列出順序錯誤、省略的分支與目前材料無法判定的部分。
不要把「入列完成」解釋成「已送達」或「已可靠保存」。
在同一個分析包目錄執行以下指令,這是新的非互動呼叫,不接續上一輪對話。若結果檔已存在,先換檔名保留前次紀錄。
Get-Content -Raw check-my-diagram.txt | claude -p --tools Read,Grep,Glob --allowedTools Read,Grep,Glob > diagram-check.md
diagram-check.md 由 PowerShell 保存 Claude 的文字回覆;工具白名單限制這次模型能呼叫的工具,不等於檔案讀取範圍隔離。核對完,再由你把接受的設計決定保存為 plan.md,交給下一輪開發。
第二份回答仍要核對。我至少抽一條跨邊界的資料流,從產生值的位置走到接收端,再把成立的結果補回計畫。遇到需求未決,就回去找能決定的人,不讓設計草稿替他批准。
原本以為只是把 false 改成 true,走完設計才發現,要交代的是四件事:
Claude Code 幫我展開資料流、比較方案與起草圖;圖讓團隊對齊理解,Git 留下修改與決定,plan.md 再把共識變成具體開發與驗收工作。查不清楚的地方仍要留下,不能靠一張完整的圖掩蓋。
設計做完,不是多了一份文件,而是下一步要改什麼、怎麼知道改對,都有了依據。 昨天的需求交接摘要走到這裡,才接得上實作。接下來,就從計畫的第一步開始:先寫測試,再讓 Claude 改程式。
參考資料:
本文實作說明
885e521,含 Domain、API、FakeSink、rules-v2.md 與 Day 9 的需求交接摘要;通知決策包含沿用項與未接受的新提案,本篇不把新提案當成已核准規格。退款旗標是記憶體標記,不連付款服務。| 實跑 | 輸入 | 參數 | 回合/秒/費用估值(美元) | 原件 |
|---|---|---|---|---|
| 設計審查包(×3) | Domain、API、FakeSink、規格、需求交接摘要 | claude -p、sonnet、medium、--safe-mode、--tools Read,Grep,Glob |
12/110/0.18;12/111/0.18;12/141/0.21 | examples/day10-design-lab/runs/design-01~03(後兩次用乾淨副本):完整回覆含 Mermaid 原圖、trace、來源雜湊。正文引第一次;循序圖順序 01、03 錯,02 對 |
對圖 seqcheck-01~03 |
只給 API 與 Domain 兩檔的乾淨副本,提示內附上面那張圖 | 同上 | 3/32/0.05;3/17/0.03;3/16/0.03 | examples/day10-design-lab/runs/seqcheck-01~03;三次皆判「圖錯:回應應在入列之後」 |
design-01 由 Codex 執行 CLI,seqcheck 由本文作者的 Claude Code session 執行;驅動端只是按下執行的角色,被評的是 Claude Code 的輸出。所有實跑為 AI 操作、唯讀分析,未修改功能、未執行 .NET 測試、未部署。verification-notes.md。引用的 Claude 回覆為原文節錄,重排格式;追蹤表為原表節錄。design-01 原始 Mermaid 與 Program.cs 第 63–89 行重繪,只呈現正常首次取消的非同步通知分支,重複取消與同步通知分支未畫。官方文件
--tools、--permission-mode plan。design.md 的架構與互動描述。實作附件
days/day10/lab-design,尚未推送,不能把 repo 首頁當成附件已公開。包內包含設計審查原件、三次對圖與 verify.py;公開後補上直接入口。閱讀既有紀錄不需呼叫模型;重跑 runner 會消耗用量,輸出不保證相同。