安安~我是ChiYu~
昨天的 Agent 很有把握地寫下:
已完成逾期處理的 Clean Code 重構。
它確實改善了命名與閱讀順序,既有行為驗證也沒有發現漂移。可是看到這份完成報告,我仍然答不出一個更基本的問題:這裡的「完成」是誰定義的?
原始 Prompt 沒有交代要清理哪一層、哪些行為不能動,也沒有設定停止條件。Agent 讀完 Repository 後,只能替我補上這些決定。
情境、範圍、意圖、證據與行為,只要有一項沒有由 User 掌握,Agent 的完成報告就可能反過來定義任務。CLEAN 要把這五項責任收回 User 手上。
《無瑕的程式碼 第二版》的〈AI、LLM 以及天知道還有什麼〉談到 Prompt Programming 如何進入程式設計,也提醒我們:AI 可以快速產生程式碼,需求、正確性、安全、架構與維護品質仍要由工程師判斷。
我把這個觀念和 Uncle Bob 的公開討論,以及自己近期大量使用 AI Coding 的實作經驗放在一起,最後整理出 CLEAN 五原則。
兩者處理同一場開發裡的不同責任:
| 層次 | 責任主體 | 負責檢查什麼 |
|---|---|---|
| Clean Code | 開發者與產出的軟體 | 從 Code、Design、Architecture 到 Craftsmanship,產出的軟體是否清楚、可理解、可修改,也值得長期維護。 |
| CLEAN | 使用 AI Agent 的 User/開發者 | User 是否提供真實情境、限制變更範圍、說清楚意圖與邊界、要求實據,並守住可預期行為。 |
Clean Code 用來審查產出的程式碼、設計與架構;CLEAN 檢查 User 如何把任務交給 AI,又如何驗收結果。證據再完整,也不會讓責任混亂的程式碼自動變乾淨。
CLEAN 不是讓 Agent 自己背五條口號;它約束的是 User 如何交付任務、授權修改與驗收結果。規則可以寫進 Prompt、AGENTS.md、任務說明或 Definition of Done,再交給 Agent 執行;需求、邊界與最終決策仍由 User 掌握。
Definition of Done 是團隊判定一項工作可以稱為完成的共同條件,例如指定測試、Diff 審查、文件與風險處理。它應在 Agent 動手前就能被查核,不能等 Agent 宣告完成後才臨時補條件。
前天先讓這五個字母和大家見面。今天,我要把它們各自負責的任務說清楚。
| 原則 | 中文名稱 | User 在 AI Coding 中負責什麼 | Review 時先問 |
|---|---|---|---|
| C — Context-Aware Code | 情境感知(具備情境感知的程式碼) | 提供並查證真實需求、Repository 規則、領域語言與既有行為,阻止 Agent 自行補完空白。 | 這項修改有專案證據,還是 Agent 覺得「一般都這樣做」? |
| L — Localized Change | 局部變更 | 切出單一變更理由,限制檔案、Diff 與副作用範圍,失敗時也能及時叫停。 | 這個需求真的需要碰這麼多地方嗎? |
| E — Explicit Intent and Boundaries | 意圖明確(明確的意圖與邊界) | 要求名稱、型別、狀態、依賴、副作用與錯誤責任都能直接讀懂。 | 規則能直接讀懂,還是得從實作細節猜? |
| A — Auditable by Evidence | 實據可審(具備實據的可審查性) | 要求並保存 Prompt、Diff、測試、工具輸出與人工決策,不接受只有完成宣告。 | Agent 說完成時,我們手上有什麼可以查? |
| N — Non-Surprising Behavior | 符合預期(零意外行為) | 定義允許與禁止改變的 API、資料、通知及失敗路徑,再用實據確認沒有未授權變更。 | 關卡全綠之後,系統有沒有偷偷換答案? |
Agent 遇到資訊缺口時,通常會拿常見做法補上。這確實很快,但它也可能把另一套專案的最佳實務搬進你的 Repository。
假設我們只說「替這支 API 加上權限」,Agent 可以自行選擇用 Role 判斷角色、用 Claim 攜帶身分資訊、建立授權 Policy,或直接在 Middleware 攔截 Request。這些技術都能處理權限,該選哪一種仍取決於專案現有的身分模型、授權規則、相容性要求與部署方式。
User 在 Agent 動手前,至少要讓它讀到以下資訊:
修改過程中,每項重要決定都應該能指出來源。來源可能是需求、程式碼、測試、README,也可能是 ADR(Architecture Decision Record,架構決策紀錄)或 User 新做出的決策。
昨天的通知次數與儲存順序來自測試和現有 Code,任務 Prompt 並沒有明寫。這些判斷雖然有專案依據,仍然只能算 Agent 推論。
如果關鍵規則找不到來源,而且不同解讀會改變系統行為,我就先停止修改。C 要求 Agent 動手前先取得這個專案真正使用的需求、規則與既有行為。
AI 產生程式碼很快,修改範圍也會跟著擴張。一句「順便整理」,可能讓 Controller、Service、DTO、測試與資料存取全部被改到。單看每一處都能說出理由,放進同一份 Diff 後卻很難安全 Review。
局部變更的單位是變更理由,檔案數只是一項線索。
API Contract 是 API 對呼叫端承諾的路由、輸入、輸出、HTTP 狀態碼與可觀察行為。為了調整同一份 Contract 而修改三個檔案,仍可能符合局部變更;只改一個檔案,卻順手整理另一個 Action,一樣算越界。L 看的是所有修改能否用同一個變更理由說明,以及失敗時能不能一起放棄。
User 要先說明:
昨天的 Agent 曾順手整理 Complete Action,最後自行撤回。那項修改本身沒有寫錯,風格也更一致;問題是它和逾期流程的清理沒有直接關係,因此不該混進同一份候選。
Diff 已無法用同一個理由說明時,就該停止並重新切分。
好名稱是 E 的起點,還不是全部。
User 還要說清楚合法狀態、依賴方向、資料修改、外部通知、錯誤語意,以及哪一層負責處理失敗。這些意圖如果只藏在方法內部,Agent 下一次修改時還是得重新推理。
昨天的候選把 items 改成 dueIncompleteWorkItems,讀者不用先進入 LINQ Query,就知道這個集合裡裝了什麼。這項命名改善是成立的。
Controller 仍然同時協調 EF Core、時間、狀態、通知與儲存。Status 仍是任意字串,通知與儲存的副作用順序也只能從實作和測試拼出來。名稱變清楚了,責任邊界尚未因此完成。
E 會持續追問:
如果重要行為仍要進入實作才能猜到,或型別還允許不合法狀態自由進入系統,E 就還有缺口。
Agent 可以整理 Prompt、Diff 與測試摘要,也能在最後列出一份很完整的完成報告。這份報告適合當作 Review 入口,不能直接替自己作證。
User 至少要能取得:
昨天的 Agent 曾用不相容的 --filter 參數執行 Microsoft Testing Platform 與 xUnit v3,實際跑到 0 項測試,Exit Code 也是代表失敗的 5。修正指令並重跑整個 Solution 後,它才取得有效的綠燈。若只留下最後一行成功摘要,前一次根本沒有跑到測試的事實就會消失。
A 要求每項結論都配得上證據強度。「已執行的測試全部通過」可以查證;「整個設計已經完成」仍需要責任、邊界與後續變更的判斷。
原始候選或驗證輸出一旦遺失,User 手上的證據就不足以支持接受決定,不能只靠 Agent 的完成宣告補上。
重構最怕出現一種結果:Code 變漂亮了,系統也偷偷換了答案。
User 要在 Agent 動手前列出允許與禁止改變的行為,包括:
昨天的驗證能支持 Route、統計語意與已測通知行為沒有漂移,無法替尚未測試的例外、併發與儲存失敗背書。
這裡需要一個可靠的 Oracle,也就是能判斷結果對錯的依據。它可能來自契約測試、驗收案例、既有資料、正式規格或人工決策。沒有 Oracle,「測試全綠」也可能只代表我們測到了自己剛寫出的答案。
N 關心的是系統對外可觀察的行為。高風險路徑沒有可靠 Oracle,或候選出現未授權漂移時,User 應停止合併。

圖:CLEAN 五原則約束的是 User 如何操作與審查 AI Agent,五項責任會依任務風險交互使用。
五項原則是並列的 Review 視角。任務可以從目前風險最高、證據最不足,或最先出現異常的那一項開始檢查。
例如測試先發現通知次數改變,這時可以從 N 開始確認外部行為。接著回到 C,查清楚重複通知究竟是既有契約、已知缺陷,還是新需求準備移除的行為。需求確認後,如果修正又產生第二個變更理由,L 就會要求拆成另一項任務。
就算 L 已經把範圍收得很小,A 也保留了完整證據,仍然不能抵銷 E 尚未說清楚的責任問題。N 也只能保護已定義的行為,不能拿一片綠燈替未知的例外與併發路徑背書。任何一項原則發現新事實,User 都可以回到對應責任重新確認,不必被 Agent 已經做了多少進度綁住。
昨天交給 Agent 的任務 Prompt 只有一句:
改善逾期處理,讓它符合 Clean Code。
套用 CLEAN 後,同一項任務可以先整理成這樣:
task:
goal: 改善逾期流程的局部可讀性
decision_owner: user
implementation_collaborator: ai_agent
C_context:
known_behavior:
- 重跑逾期項目時仍會再次通知
- 通知發生在資料儲存之前
unknown:
- 未定義通知例外與併發行為
L_scope:
allowed:
- ProcessOverdue 內的命名、函式與局部組織
forbidden:
- 其他 Action
- 新增 Service、Repository 或 Outbox
E_intent:
protected_contract:
- Route、HTTP Status Code 與回應統計
side_effect:
- 保留通知與儲存順序
A_evidence:
required:
- 原始 Prompt、模型、起始版本與候選版本
- 完整 Diff、測試輸出與人工 Review
N_behavior:
forbidden_change:
- 通知次數
- 重跑語意
- API Contract
stop_when:
- 關鍵行為找不到專案來源
- Diff 出現第二個變更理由
- 候選改變禁止修改的行為
這段 YAML 不會讓 Production Code 自動變乾淨。它先把決策權放回 User 手上:誰定義需求、哪些範圍能改、什麼證據算完成,以及哪種情況必須叫停。
團隊可以把這份契約寫成 Markdown、Issue Template、AGENTS.md、Skill,或放進自己的任務系統。使用哪種格式都可以,五項 User 責任與停止條件要能被 Agent 和 Reviewer 直接查到。
今天不再產生新的 Production Code,而是固定使用昨天保存的候選。這樣五項原則面對的是同一份輸入,差異只剩各自檢查的責任。
| 原則 | 昨天的候選讓我看到什麼 |
|---|---|
| C — Context-Aware Code 情境感知 | Agent 的邊界推論有 Repository 線索,任務 Prompt 本身仍然模糊。 |
| L — Localized Change 局部變更 | 逾期流程維持單一變更理由,無關 Action 的修改被撤回。 |
| E — Explicit Intent and Boundaries 意圖明確 | 命名與閱讀入口改善,Controller 的責任與副作用邊界仍未改變。 |
| A — Auditable by Evidence 實據可審 | 原始 Prompt、Diff、失敗與重新驗證都能被重查,完成報告不必直接相信。 |
| N — Non-Surprising Behavior 符合預期 | 已測行為沒有漂移,例外、併發與儲存失敗仍屬未知。 |
我會讓這份候選進入 Review。它有 Repository 線索、修改集中,命名與閱讀入口也有改善,原始證據仍能回查。不過 Controller 的責任尚未重新分配,例外、併發與儲存失敗也還是未知,因此不能稱為完整重構。這樣的判讀比單純寫「成功」更能幫助 User 決定下一步。
如果每次叫 Agent 改一個區域變數,都要填完一份完整契約,這套方法很快就會被團隊放棄。
CLEAN 要檢查到多深,取決於修改風險。區域變數改名可以只保留明確目的、聚焦這次目的的 Diff、相關測試與人工接受理由;資料、通知、授權、交易或併發變更,則要完整檢查 Context、修改半徑、狀態與副作用、原始證據及失敗行為。
能調整的是文件與驗證深度。User 對需求、範圍與最終結果的責任不會跟著縮小。
我目前會用三個問題決定 CLEAN 要開到多重:
只要其中一題的代價很高,就不該只看 Agent 的完成摘要。
前天第一次介紹 CLEAN 時,我只說明五個字母各自負責什麼。今天再往下補齊 User 動作、檢查方式與停止條件:
接下來,產出的程式碼仍會接受 Clean Code 對命名、函式、物件、類別、測試、設計、架構與工藝的檢查。我不會在每篇文章硬塞五項 CLEAN,只帶入當下真正需要的原則,觀察同一支 API 在不同修改壓力下會出現什麼效果、成本與取捨。
明天先從最局部的地方開始:一段 Code 變短、名稱變清楚、閱讀順序變整齊之後,下一次修改真的會比較容易嗎?
今天沒有新增 Production Code。這篇只建立 CLEAN 協作契約,並套用在昨天已保存的候選上。
git fetch origin --tags
git switch --detach day-02-sol-high-candidate-run-01
git rev-parse HEAD
最後一行應顯示 59a3ee9a1ff55e1aa18f4de3209c975b31410486。
完整資料:
series-behavior-baseline-v1,系列行為基準。day-02-sol-high-candidate-run-01,昨天產生的 Sol/high 候選。