iT邦幫忙

2026 iThome 鐵人賽

DAY 1
0

計畫裡,已經寫好了要驗證的行為

IMS 的實作計畫放在 docs/plans/。Task 2 是「Models + JSCore 解析」,第一步就列出了解析功能要通過的測試。下面是其中的節錄:

func testParsesBundledSchedule() throws {
    let parsed = try ScheduleParser.parse(
        scheduleJS: fixture("schedule.bundle.js"),
        correctionsJS: fixture("corrections.bundle.js"))
    XCTAssertTrue(parsed.people.contains("DinDin"))
    // 修正檔會改名 → 驗證修正有生效
    XCTAssertFalse(parsed.schedule.contains { $0.person == "<舊名>" })
}

func testCorruptCorrectionsFallsBackToScheduleOnly() throws {
    let parsed = try ScheduleParser.parse(
        scheduleJS: fixture("schedule.bundle.js"),
        correctionsJS: "this is not js ((")
    XCTAssertTrue(parsed.people.contains("DinDin"))  // 仍可解析
}

這段節錄依賴專案的測試資料與 fixture 輔助函式,使用時要對應實際資料;<舊名> 也是需要替換的示意值。

第二個測試把一個決策寫得很具體:修正檔無法解析時,仍要能從主檔取得資料。實作還沒開始,我就能先檢查這是不是想要的行為。

計畫要讓執行者知道,最後必須交出什麼證據。 只寫「完成解析功能」,AI 還得自行猜測錯誤時如何處理;列出測試後,預期行為就有了可執行的例子。符合它的實作可能不只一種,但不能任意改變這個結果。

計畫是給沒參與前面對話的人看的

我把實作計畫當成「外接記憶」,因為它有兩種讀者。

第一種是接手工作的 AI。在我的派工方式裡,每個 task 會交給一個獨立執行者,只提供相關的計畫、專案規則與決策。它不需要讀完整段聊天歷史,卻必須有足夠資訊完成工作。

因此,計畫不能只寫「照剛才說的做」或「類似 Task 3」。需要引用時,要指出具體檔案、段落或函式,讓執行者能找到依據。

第二種是幾個月後的自己。回頭維護時,我可能已經忘了為什麼 Widget 只讀快照、JavaScriptCore 必須留在 App。計畫和決策紀錄能把這些理由接回來。

這幾份文件各有分工:

文件 回答的問題
Ticket/規格 要完成什麼?如何驗收?
決策紀錄 為什麼這樣選?哪些替代方案已經考慮過?
實作計畫 按什麼順序做?每一步改哪裡、怎麼驗證?

文件可以互相引用,不必重複貼上所有內容。重要的是下一個讀者找得到答案。

一個 task,要寫到能直接開始執行

我習慣在每個 task 裡放五個元素:

  1. 檔案範圍:要新增、修改與測試哪些檔案。
  2. 測試案例:輸入、預期輸出,以及要確認的行為;邏輯任務可以直接附測試程式碼。
  3. 實作範圍:這一步負責什麼,哪些設計必須沿用。
  4. 驗證指令:在這個專案裡實際能執行的命令。
  5. 成功條件:要看到哪些測試執行、哪些結果,才算通過。

以 BlockBuilder 為例,它需要將指定人員的時段排序,再依規則合併成任務區段。計畫不能只寫「把相鄰任務合併」,還要交代同角色、休息時段與真正空檔的差別。

涉及前置 task 的型別,也要寫清楚依賴。如果 TaskBlock 尚未建立,就標明由哪個 task 定義,不能讓執行者自己猜介面。

我通常讓一個步驟只有一個可檢查的目的,例如補一個測試、確認失敗、完成一段邏輯。若一個 task 預計超過半小時,或包含好幾種互不相關的行為,就檢查能否拆小。時間是提醒,真正的判斷是中間出了問題,能不能快速定位。

人審計畫時,先看測試要證明什麼

AI 寫出計畫後,我會先讀測試名稱與斷言,再檢查架構和修改範圍。每個測試都問兩件事:

  • 它對應 ticket 的哪一條完成條件?
  • 如果這個行為做錯,它有沒有機會抓到?

IMS 的任務合併測試包含同角色相鄰時段、不同角色、休息區段、真正空檔、重複資料、人員篩選、時區與排序。其中「休息時段可以被吸收」是我特別補的。

例如,同一個人 9:40–10:20 在外場,10:20–10:30 是排班表明列的休息,10:30–10:50 又回到同一角色。對這個 App 來說,這三段可以形成 9:40–10:50 的一個任務區段,避免同一段勤務被拆成多次提醒。

但如果中間根本沒有排班資料,只是一段真正的空檔,就不能一律合併。這個差別只靠「相鄰任務要合併」很難說清楚,用兩組測試就能分別表達。

測試不是完整需求的替代品;它能幫你檢查,文字需求是否已經轉成正確的判斷規則。

今天要做的一件事:把 ticket 轉成可執行的計畫

準備好 ticket 和 Day 8 的決策紀錄,請 AI 先列測試案例,再寫詳細步驟:

根據指定的 ticket 和 docs/decisions-log.md,撰寫實作計畫。
先讀 CLAUDE.md 與相關程式碼,確認既有架構與測試方式。

第一階段:
- 列出 task、依賴順序、測試名稱和關鍵斷言。
- 每個測試標出對應的完成條件。
- 沒有辦法用單元測試驗證的項目,列出其他驗證方式。
先交給我檢查,再寫完整計畫。

第二階段:
- 每個 task 列檔案、測試案例、實作範圍、完整驗證指令與成功條件。
- 邏輯任務附測試程式碼;所需型別與測試資料要有來源。
- 每個 task 預計不超過 8 個檔案,超過則說明拆分方式。
- 阻擋實作的未知事項單獨列出,不用 TBD 代替。
- 存到 docs/plans/<日期>-<名稱>.md,先不要開始實作。

檢查時,把案例逐一對回完成條件,補上遺漏的邊界情況。完整計畫寫好後,再看檔案範圍、依賴和驗證指令,確認另一個執行者能靠它開工。

核准後納入版本控制。後續若需求或決策改變,也要同步更新計畫,避免文件與實作各走各的。

新人最常在這一步犯的錯

計畫只有大標題。 「Task 1:做資料層」沒有交代輸入、輸出或驗證方式,執行者仍得做大量猜測。

Task 包得太大。 資料解析、通知和 UI 放在同一步,出問題時很難判斷是哪一層。依可驗證的行為拆分。

只審措辭,沒審斷言。 計畫文字再順,測試若期待了錯誤行為,實作仍會往錯的方向走。

關鍵值沒有來源。 日期、時區、URL、timeout 和上限,都要有規格或決策依據,不能用看起來合理的值填空。

計畫核准後就不再更新。 後續接手的人會依據舊文件工作。改了重要決策,就把新結論寫回去。

本篇可帶走的檔案

plan-task-template.md 是計畫的骨架。先填共用限制,再依需要複製 task 段落;範例中的佔位內容要在開工前補齊。

# <功能名稱>實作計畫

## 需求與決策來源

- Ticket:<路徑>
- 決策紀錄:<路徑與相關條目>

## 不可自行決定的事項

- 數值:<timeout、上限、間隔的來源>
- 日期/時區:<來源>
- URL/識別字:<來源>
- 架構限制:<哪些設計必須沿用>

### Task N:<名稱>

**依賴:** <前置 task 或既有型別>
**檔案:** 新增 `<path>`;修改 `<path>`;測試 `<path>`
**完成條件:** <對應 ticket 的哪一項>

**Step 1:失敗測試**

```swift
// 填入可執行的測試,包含具體輸入與預期結果。
```

**Step 2:** 執行測試,檢查實際失敗原因是否符合預期。
**Step 3:** <實作範圍、要沿用的函式與限制>
**Step 4:** 再跑測試,確認新行為通過、既有行為沒有退步。
**Step 5:** 交出 diff 和驗證結果,依約定審查後 commit。

**完整驗證指令:** <包含 scheme、執行環境與測試範圍>
**成功條件:** <預期執行的測試、通過結果、其他必要檢查>
**需另外驗證:** <真機、畫面或整合行為;無則註明>

派工時,要把共用限制和相關 task 一起交給執行者。文件寫在前面只是第一步,還要確保接手的人知道去哪裡讀。


上一篇
Day 8|Grill:讓 AI 審訊你的需求
下一篇
Day 10|先寫會失敗的測試:TDD 在 AI 協作下的角色
系列文
轉生到 AI 世界,帶著兩個 AI 隊友獨自進化 共 10 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言