iT邦幫忙

2026 iThome 鐵人賽

DAY 3
0
Software Development

AI 時代的 Clean Code:30 天讓 AI 產出的程式碼可讀、可驗證、可維護系列 第 3

Day 3|CLEAN 五原則:如何掌握 AI Coding 的情境、範圍、意圖、證據與行為?

  • 分享至 

  • xImage
  •  

安安~我是ChiYu~

昨天的 Agent 很有把握地寫下:

已完成逾期處理的 Clean Code 重構。

它確實改善了命名與閱讀順序,既有行為驗證也沒有發現漂移。可是看到這份完成報告,我仍然答不出一個更基本的問題:這裡的「完成」是誰定義的?

原始 Prompt 沒有交代要清理哪一層、哪些行為不能動,也沒有設定停止條件。Agent 讀完 Repository 後,只能替我補上這些決定。

情境、範圍、意圖、證據與行為,只要有一項沒有由 User 掌握,Agent 的完成報告就可能反過來定義任務。CLEAN 要把這五項責任收回 User 手上。

CLEAN 是我從 Clean Code 延伸到 AI 協作的五項 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、資料、通知及失敗路徑,再用實據確認沒有未授權變更。 關卡全綠之後,系統有沒有偷偷換答案?

C — Context-Aware Code 情境感知:先讓 Agent 讀到真實專案

Agent 遇到資訊缺口時,通常會拿常見做法補上。這確實很快,但它也可能把另一套專案的最佳實務搬進你的 Repository。

假設我們只說「替這支 API 加上權限」,Agent 可以自行選擇用 Role 判斷角色、用 Claim 攜帶身分資訊、建立授權 Policy,或直接在 Middleware 攔截 Request。這些技術都能處理權限,該選哪一種仍取決於專案現有的身分模型、授權規則、相容性要求與部署方式。

User 在 Agent 動手前,至少要讓它讀到以下資訊:

  • 真實需求與驗收條件。
  • Repository Instruction 與團隊規範。
  • 專案實際使用的領域語言。
  • 目前已存在的行為與相容性限制。
  • 已知假設、非目標與尚未確認的問題。

修改過程中,每項重要決定都應該能指出來源。來源可能是需求、程式碼、測試、README,也可能是 ADR(Architecture Decision Record,架構決策紀錄)或 User 新做出的決策。

昨天的通知次數與儲存順序來自測試和現有 Code,任務 Prompt 並沒有明寫。這些判斷雖然有專案依據,仍然只能算 Agent 推論。

如果關鍵規則找不到來源,而且不同解讀會改變系統行為,我就先停止修改。C 要求 Agent 動手前先取得這個專案真正使用的需求、規則與既有行為。

L — Localized Change 局部變更:每一行 Diff 都要屬於同一個理由

AI 產生程式碼很快,修改範圍也會跟著擴張。一句「順便整理」,可能讓 Controller、Service、DTO、測試與資料存取全部被改到。單看每一處都能說出理由,放進同一份 Diff 後卻很難安全 Review。

局部變更的單位是變更理由,檔案數只是一項線索。

API Contract 是 API 對呼叫端承諾的路由、輸入、輸出、HTTP 狀態碼與可觀察行為。為了調整同一份 Contract 而修改三個檔案,仍可能符合局部變更;只改一個檔案,卻順手整理另一個 Action,一樣算越界。L 看的是所有修改能否用同一個變更理由說明,以及失敗時能不能一起放棄。

User 要先說明:

  • 這次唯一的變更理由。
  • 允許修改的檔案、模組或責任。
  • 明確禁止順手整理的區域。
  • 出現第二個責任時,要停止還是另開任務。

昨天的 Agent 曾順手整理 Complete Action,最後自行撤回。那項修改本身沒有寫錯,風格也更一致;問題是它和逾期流程的清理沒有直接關係,因此不該混進同一份候選。

Diff 已無法用同一個理由說明時,就該停止並重新切分。

E — Explicit Intent and Boundaries 意圖明確:讓規則與副作用不用靠猜

好名稱是 E 的起點,還不是全部。

User 還要說清楚合法狀態、依賴方向、資料修改、外部通知、錯誤語意,以及哪一層負責處理失敗。這些意圖如果只藏在方法內部,Agent 下一次修改時還是得重新推理。

昨天的候選把 items 改成 dueIncompleteWorkItems,讀者不用先進入 LINQ Query,就知道這個集合裡裝了什麼。這項命名改善是成立的。

Controller 仍然同時協調 EF Core、時間、狀態、通知與儲存。Status 仍是任意字串,通知與儲存的副作用順序也只能從實作和測試拼出來。名稱變清楚了,責任邊界尚未因此完成。

E 會持續追問:

  • 名稱有沒有使用專案真正的領域語言?
  • 型別能不能排除不合法狀態?
  • 依賴方向是否清楚?
  • 哪裡會修改資料、呼叫外部服務或產生通知?
  • 失敗與取消由誰處理,又會留下什麼結果?

如果重要行為仍要進入實作才能猜到,或型別還允許不合法狀態自由進入系統,E 就還有缺口。

A — Auditable by Evidence 實據可審:完成報告先當成待查主張

Agent 可以整理 Prompt、Diff 與測試摘要,也能在最後列出一份很完整的完成報告。這份報告適合當作 Review 入口,不能直接替自己作證。

User 至少要能取得:

  • 原始 Prompt、模型、推理強度與起始版本。
  • 未經人工修飾的候選與完整 Diff。
  • 實際執行的命令、Exit Code(命令結束時回傳的狀態碼)、測試與工具輸出。
  • 中途失敗、修正過程與仍未驗證的範圍。
  • 人工最後接受、限縮或拒絕的理由。

昨天的 Agent 曾用不相容的 --filter 參數執行 Microsoft Testing Platform 與 xUnit v3,實際跑到 0 項測試,Exit Code 也是代表失敗的 5。修正指令並重跑整個 Solution 後,它才取得有效的綠燈。若只留下最後一行成功摘要,前一次根本沒有跑到測試的事實就會消失。

A 要求每項結論都配得上證據強度。「已執行的測試全部通過」可以查證;「整個設計已經完成」仍需要責任、邊界與後續變更的判斷。

原始候選或驗證輸出一旦遺失,User 手上的證據就不足以支持接受決定,不能只靠 Agent 的完成宣告補上。

N — Non-Surprising Behavior 符合預期:先定義哪些答案不能改

重構最怕出現一種結果:Code 變漂亮了,系統也偷偷換了答案。

User 要在 Agent 動手前列出允許與禁止改變的行為,包括:

  • API Route、Request、Response 與 HTTP Status Code。
  • 資料狀態、預設值與資料庫契約。
  • 通知、付款、檔案寫入等外部副作用。
  • 執行順序、重試、取消與例外行為。
  • 重複 Request、併發與失敗後留下的狀態。

昨天的驗證能支持 Route、統計語意與已測通知行為沒有漂移,無法替尚未測試的例外、併發與儲存失敗背書。

這裡需要一個可靠的 Oracle,也就是能判斷結果對錯的依據。它可能來自契約測試、驗收案例、既有資料、正式規格或人工決策。沒有 Oracle,「測試全綠」也可能只代表我們測到了自己剛寫出的答案。

N 關心的是系統對外可觀察的行為。高風險路徑沒有可靠 Oracle,或候選出現未授權漂移時,User 應停止合併。

User 透過情境感知、局部變更、意圖明確、實據可審與符合預期駕馭 AI Agent

圖:CLEAN 五原則約束的是 User 如何操作與審查 AI Agent,五項責任會依任務風險交互使用。

CLEAN 五原則不能加總成品質分數,也不必照 C 到 N 執行

五項原則是並列的 Review 視角。任務可以從目前風險最高、證據最不足,或最先出現異常的那一項開始檢查。

例如測試先發現通知次數改變,這時可以從 N 開始確認外部行為。接著回到 C,查清楚重複通知究竟是既有契約、已知缺陷,還是新需求準備移除的行為。需求確認後,如果修正又產生第二個變更理由,L 就會要求拆成另一項任務。

就算 L 已經把範圍收得很小,A 也保留了完整證據,仍然不能抵銷 E 尚未說清楚的責任問題。N 也只能保護已定義的行為,不能拿一片綠燈替未知的例外與併發路徑背書。任何一項原則發現新事實,User 都可以回到對應責任重新確認,不必被 Agent 已經做了多少進度綁住。

把一句模糊 Prompt 改成可停止的 CLEAN 契約

昨天交給 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 直接查到。

用 CLEAN 五個視角重新審查昨天的候選

今天不再產生新的 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 決定下一步。

低風險任務使用輕量 CLEAN,高風險副作用加重驗證

如果每次叫 Agent 改一個區域變數,都要填完一份完整契約,這套方法很快就會被團隊放棄。

CLEAN 要檢查到多深,取決於修改風險。區域變數改名可以只保留明確目的、聚焦這次目的的 Diff、相關測試與人工接受理由;資料、通知、授權、交易或併發變更,則要完整檢查 Context、修改半徑、狀態與副作用、原始證據及失敗行為。

能調整的是文件與驗證深度。User 對需求、範圍與最終結果的責任不會跟著縮小。

我目前會用三個問題決定 CLEAN 要開到多重:

  1. 這項修改會不會改變外部可觀察行為?
  2. 發生錯誤時,資料、金流、授權或通知能不能安全復原?
  3. 候選若判斷錯誤,我能不能快速放棄並回到已知狀態?

只要其中一題的代價很高,就不該只看 Agent 的完成摘要。

Clean Code 守住產出,CLEAN 守住協作方向

前天第一次介紹 CLEAN 時,我只說明五個字母各自負責什麼。今天再往下補齊 User 動作、檢查方式與停止條件:

  • C:掌握真實情境。
  • L:限制變更範圍。
  • E:說清楚意圖與邊界。
  • A:留下可審查實據。
  • N:守住可預期行為。

接下來,產出的程式碼仍會接受 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

完整資料:

參考資料


上一篇
Day 2|叫 AI 清理程式碼,為什麼常只清到表面?
系列文
AI 時代的 Clean Code:30 天讓 AI 產出的程式碼可讀、可驗證、可維護3
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言