Pseudo-Code Partner — 五步工作流詳解

本檔為 SKILL.md 引用的詳細工作流指南。在每次虛擬碼會談開始前讀一次。


前置條件

進入虛擬碼階段前確認:

  1. OOA 文件存在spec/{feature}/{feature}.ooa.md 或同等 markdown,包含 class diagram、domain vocabulary
  2. functional spec 可參照spec/{feature}/{feature}.fp.md 提供 use case、I/O 範例、邊界條件編號(測試對應會用到)
  3. 使用者明確要做這份功能的虛擬碼 — 不是「順便聊聊」

不符合任一條 → 明說缺什麼、停下。不要替使用者捏造 OOA。


步驟 1 — 定位 OOA 輸入

目標:取得單一真實來源(single source of truth),作為虛擬碼的輸入。

操作

  1. Read spec/{feature}/{feature}.ooa.md
  2. 確認包含三項要素:
    • 領域詞彙(沒有 → 步驟 2 自己萃取,但耗時)
    • 類別圖(沒有 → 警告使用者,建議補 OOA)
    • 設計意圖(沒有 → 一邊寫虛擬碼一邊回問)
  3. Read spec/{feature}/{feature}.fp.md(若存在),記下 use case 與邊界條件編號

與使用者對齊

我讀過 spec/{feature}/{feature}.ooa.md,看到 {N} 個類別(其中 {K} 個含業務決策方法)。
我也讀過 spec/{feature}/{feature}.fp.md,{M} 個 use case、{L} 條邊界條件。

我會以這兩份為輸入。虛擬碼若需要新增 OOA 沒提到的類別,我會先停下來問你。
從 use case {第一個} 開始展開,OK?

異常

  • OOA 不存在或殘缺 → 「上游沒有完整 OOA,我這層無法捏造。要回去補還是先停?」由使用者決定。
  • functional spec 不存在 → 仍可進行,但測試對應只能對齊到 OOA 層級的描述,提醒使用者測試覆蓋面會打折。

步驟 2 — 建立名詞表 §0

目標:確保虛擬碼中每個出現的詞都有定義,避免「pending 是什麼?」這類 review 噪音。

操作

  1. 從 OOA domain vocabulary 萃取所有名詞
  2. 加入動詞 / 動作(OOA 通常沒有,需從 class diagram 的 method 名稱推導)
  3. 列為 §0. 名詞表,格式:
    | 名詞 | 中文 | 定義 |
    |---|---|---|
    | WorkCenter | 工作中心 | 一組執行同類加工的資源集合 |
    | pending | 待排工單 | 尚未被任一 SlidingWindow 排入的 RSR |
    

對齊原則

  • 名詞必須與 class diagram 命名一致
  • 動詞用主動式(calculateassign),不用 xxxService.doXxx() 的內部呼叫
  • 不確定的詞 不寫進虛擬碼 — 強制使用者澄清

步驟 3 — 逐 Use Case 展開虛擬碼

目標:把 OOA 的 class method 展開為完整演算法。

操作

對於每個需要展開的 method(通常是 Domain Service、Use Case、Strategy 的 entry method):

  1. 確認入口:method signature(不含型別,只看名稱與職責)
  2. 列高階步驟:3–7 個步驟,每步是一句話
  3. 逐步展開為虛擬碼:用 §X.Y.Z 編號
  4. 使用者確認 → 才前進下一個 use case

演算法語法

function methodName(arg1, arg2):
    // §X.Y.Z 步驟說明
    if condition:
        action
    while condition:
        action
    for item in collection:
        action
    raise SomeException
    return result

編號規則

  • 階層式:§1 大流程,§1.1 子流程,§1.1.1 步驟
  • 新增:附加到末尾,不重排(例:原本最深到 §1.4,新加為 §1.5)
  • 廢棄:保留段落,標 ~~刪除線~~ + 廢棄原因
  • 重大重寫:開新版本檔(fine-schedule-v2.md),舊版保留

何時拆子方法

如果 §X.Y.Z 內含 ≥ 4 個子步驟,且這些子步驟有獨立的業務意涵:拆出子方法,編號 §X.Y.(N+1)。

何時不拆

  • 純技術細節(loop 內的計算、collection 操作)
  • 沒有獨立業務意涵(純為了讓 §X.Y.Z 行數短)

設計分歧處理

每個 §X.Y.Z 寫完問自己:「有沒有其他合理寫法?」常見分歧:

  • 及早 vs. 延遲(Eager vs. lazy) — 一次算完 vs. 分批算
  • 推 vs. 拉(Push vs. pull) — 上游推 vs. 下游拉
  • 貪婪 vs. 回溯(Greedy vs. backtracking) — 局部最優 vs. 全域搜尋
  • 先過濾再映射 vs. 先映射再過濾(Filter then map vs. map then filter) — 兩種 pipeline 順序

有分歧 → 寫進「設計理由」段,說明選擇與替代方案。


步驟 4 — 標註對應實作

目標:把虛擬碼章節綁定到具體 class / method,作為實作契約。

操作

每個 §X.Y(子流程級)必須有:

**對應實作**`com.leanplay.fineschedule.application.FineScheduler#run`

推導規則

  1. Package — 從 OOA 已決定的 module 與 layer 推
  2. Class — 從 OOA class diagram 取
  3. Method — 從 use case 名稱駝峰化

衝突處理

若推導出的 class 不在 OOA:

  • 小幅補充(多一個輔助類別)→ 補進 OOA 後再回來
  • 大幅重構(質疑 OOA 切分)→ 停下,與使用者對齊:要回去改 OOA 還是調整虛擬碼結構

步驟 5 — 標註測試對應

目標:把虛擬碼分支綁回 functional spec 的 I/O 範例與邊界條件編號,作為 Stage 5 測試的藍圖。

操作

每個 §X.Y.Z(步驟級)若是分支或例外點,必須標:

**測試對應**
- 正常:functional-spec §UC A1 正常值(quantity = 100)
- 異常:functional-spec §{SHORT}-FS-2(quantity ≤ 0 → InvalidQuantityException)
- 邊界:functional-spec §{SHORT}-FS-5(quantity 剛好 = capacity max)

若 functional spec 沒涵蓋

虛擬碼階段可以反向推動 functional spec 補完。常見情況:

  • 虛擬碼寫到 raise InvalidQuantityException → functional spec 必須有對應邊界條件
  • 虛擬碼寫到邊界(while cursor < end)→ functional spec 必須有邊界值 I/O 範例

此時提醒使用者:上游 functional spec 缺一條邊界,建議補回去再繼續。不要自己默默假設一條測試。


最終輸出

當所有 use case 完成:

  1. 套用 output-template.md 的格式組裝完整文件
  2. 寫入 spec/{feature}/{feature}.pseudo.md
  3. 文件底部留簽核欄位(姓名 + 日期),由使用者自行填
  4. 提示使用者:
    • 路徑可加進 ticket / 索引
    • 實作時為目標類別補 Javadoc §編號 引用,建立 code → pseudo-code 反向追溯

異常情境

使用者要求跳過某步

不允許跳過 步驟 1(OOA 確認)與 步驟 5(測試對應)。其他步驟可在使用者堅持下簡化,但需警告後果。

寫到一半發現 OOA 有錯

立刻停止,告知使用者:「§X.Y 寫不下去,因為 OOA 把 A 跟 B 混為一個類別。」由使用者決定是回去改 OOA 還是在虛擬碼這層繞過(後者通常是壞訊號)。

使用者要求「一次給我看全部」

拒絕。理由:

  • 一次給全部 → review 變成走過場
  • 一次給全部 → 中間發現問題會骨牌式重寫
  • 一步一確認 → 慢但穩

但可以承諾節奏:「我估算 §1 大約有 5 個子流程,每個 5–10 分鐘,總共 30 分鐘左右。可以嗎?」