
Day 08 已用測試保護「每天九點」的時區計算。這次我往排程外移一層,審查新增的匯入閘門。我讀這小段 Java 程式差異(diff)時,視線先停在 strip 與 toLowerCase;把集合操作排成時序後,才發現互斥判斷被拆成兩步。ChatGPT 可以擴大搜尋面,回覆仍不是合併依據。
OpenAI 目前把理解程式碼庫、執行測試與審查變更列為 Codex 工作;本篇的模擬初篩只處理已提供的需求與 diff,不直接操作專案。三層分工如下:
| 審查層次 | 我交給它的工作 | 為何不能單獨作為合併依據 |
|---|---|---|
| ChatGPT 初篩 | 找空值、並行衝突與測試缺口 | 不知道未提供的程式與業務脈絡 |
| 編譯與 JUnit 5 | 重現輸入、時序與實際結果 | 通過只代表已寫下的條件成立 |
| 人工程式碼審查 | 對照需求、否決誤報、決定是否擋下 | 合併責任仍由團隊承擔 |

我只問每一點能否轉成失敗斷言;無法重現的先留問題單。三層涵蓋範圍不同,順序可依習慣調整。
完整提示詞 先交代兩條本文演練假設:同一個 workspaceId 同時間只能有一批匯入,前後空白與英文大小寫不影響識別。兩者尚未寫入 v0 需求,正式開發前仍須確認。模擬輸入只包含 待審 diff,不要求重寫整個類別。
if (runningWorkspaces.contains(key)) {
return false;
}
runningWorkspaces.add(key);
return true;
完整對話紀錄。回覆列出 null 的 NullPointerException 與 contains/add 競爭條件;我再追問測試名稱、斷言與假陰性風險。兩項標為 BLOCK,在 JUnit 5 重現前仍待驗證。

我補了四項 JUnit 5 測試。修正前測試 只多一個讓測試能換入自訂集合的建構子,業務方法仍與待審 diff 相同。CyclicBarrier 會在測試用的 BarrierSet.add 攔住兩個呼叫,等到齊再放行到實際集合。修正前結果是 Tests run: 4, Failures: 3:null 例外錯誤、兩次啟動都回傳成功,還有模擬初篩刻意漏掉的第三項——tryStart 存入正規化鍵,finish 卻用原字串移除,工作完成後仍無法再次啟動。
最小修正沒有替整個方法加鎖,而是使用 ConcurrentHashMap.newKeySet() 所提供集合的單次 add 完成「檢查並登記」,再用回傳值判斷是否啟動;開始與完成也共用同一個 normalize:
public boolean tryStart(String workspaceId) {
return runningWorkspaces.add(normalize(workspaceId));
}
public void finish(String workspaceId) {
runningWorkspaces.remove(normalize(workspaceId));
}
重跑 mvn clean test 後,四項測試全部通過。完整程式、測試與驗證紀錄 也保留了修正前後的結果。

補作回覆另列一項 VERIFY:ConcurrentHashMap.newKeySet() 的並行保證須查文件。我查 Java 17 的應用程式介面(Application Programming Interface,API)後排除此疑點:單次容器操作安全,錯在檢查與登記分成兩次。finish 鍵值不一致仍由人工補上。
| 問題 | 模擬初篩 | 測試/文件 | 最終結論 |
|---|---|---|---|
| null 例外型別 | 找到 | JUnit 5 紅燈 | 修正 |
contains+add |
找到 | 並行測試紅燈 | 改用單次 add |
newKeySet 並行保證 |
待查 | Java API 文件 | 排除 |
finish 鍵不一致 |
漏報 | 人工補測試 | 修正 |

匯入閘門只保護一份 Java 服務;多份同時執行仍需資料庫等共用機制。原始碼也只能放進核准工作區。表格故意保留待查與漏報,用來示範複核流程。
這份模擬初篩先攤開可能的失敗條件,JUnit 5 與官方文件留下可重現證據,人工審查再補上遺漏脈絡。從 Day 05 到今天,同一份需求已由問題清單走到能執行的驗證。接下來 Day 10 會先講一個故事,定調接下來人類與 Codex 該怎麼合作,再實際讓 Codex 進入工作目錄,觀察代理如何完成一輪任務。