Claude Code 教學:一些實作上的眉眉角角

前一篇文章「 鄭智維先生的 Claude Code 實戰指南:30 個技巧 + 8 個踩坑」是根據鄭先生臉書分享的圖片,由 Claude 衍生展開。這一篇則是由 Claude 自行撰寫。

在開始之前,先說一點心得:

如果沒有經過這樣的過程,而是一開始就直接請 Claude 撰寫「Claude Code 實作指引」的話,內容應該會是現在的十分之一不到。

這不是 AI 的錯,是我們下的指令不對。

所以,未來我們要請 Claude 撰寫某個主題的文章時,如何更完整呢?

簡單的說:提供一個 roadmap。這個 roadmap 告訴 Claude 如何進行,有了這個 roadmap (就像是 Claude Code 的 Claude.md),Claude 就不只是撰寫一篇文章,而是根據 roadmap 上的每個點,有歷史脈絡的各自撰寫一篇文章。

以本文為例,roadmap 如下(點擊展開):

用使用者心智模型的成熟度來組織結構,而不是用難度分層。

原作者是以「功能介紹」為主軸,Claude 的版本則是以「你可能會在哪裡出問題」為主軸。

新增了指令設計、驗證與品質控制、團隊協作等新主題,以及失敗模式的系統分析。

一、重新認識這個工具

先破除錯誤預設,再建立正確操作模型

  1. Claude Code 的操作模型:Agent,不是 Assistant
  2. Context Window 是最重要的資源,不是功能
  3. Session 是工作單位,不是對話單位
  4. Claude Code 看到的世界:它能感知什麼、感知不到什麼
  5. 它的信心和它的準確率是兩件事

二、指令設計

這是最被低估的核心技能

  1. 描述結果,不描述步驟
  2. 給約束,不給自由
  3. 把驗證標準寫進指令
  4. 用檔案路徑代替描述
  5. 歧義是你的責任,不是它的問題

三、工作流程設計

任務怎麼拆、Session 怎麼管

  1. 任務拆解的原則:原子性和可驗證性
  2. Session 邊界的設計
  3. Git 是 Session 之間唯一可靠的記憶
  4. Worktree 讓並行成為可能
  5. Human-in-the-loop 不是效率問題,是風險管理

四、長期記憶系統

CLAUDE.md 和 Skill 的完整設計

  1. CLAUDE.md 的三層結構:專案、模組、個人
  2. 什麼該寫進 CLAUDE.md,什麼不該
  3. Skill 的參數化設計
  4. 設定檔納入版本控制的團隊意義
  5. 記憶系統的維護:什麼時候該更新

五、驗證與品質控制

新增主題

  1. 「完成」的定義要在任務開始前就說清楚
  2. 測試先行的工作流程
  3. 讓它 review 自己的輸出
  4. 讓它 review 你的輸出
  5. 什麼程度的輸出值得信任,什麼程度需要人工複查

六、工具整合

MCP 和外部系統

  1. MCP 的正確心智模型:能力擴充,不是魔法
  2. 選現成 MCP 還是自建的判斷標準
  3. 自建 MCP 的最小可行設計
  4. MCP 的 debug 方法論
  5. 工具鏈的複雜度管理

七、常見失敗模式

更系統化的進化版踩坑區

  1. 速度陷阱:跑得快但方向錯
  2. 信任陷阱:它說完成就以為完成
  3. 脈絡陷阱:Session 狀態污染
  4. 規模陷阱:小資料通過、真實環境爆炸
  5. 依賴陷阱:套件升級的隱性風險
  6. 理解陷阱:codebase 是它寫的但你看不懂

八、團隊協作

新增主題

  1. 讓 Claude Code 的行為在團隊內一致
  2. Skill 和 CLAUDE.md 的共同維護機制
  3. AI 生成的程式碼如何做 code review
  4. 什麼應該讓 Claude Code 做,什麼應該人自己寫
  5. 團隊對 Claude Code 輸出的共同品質標準

▌1. 重新認識這個工具

學習操作技巧之前,先調整好我們的預設認知。錯誤的心智模型(我們對 Claude Code 運作方式的預設理解想像)會讓我們把每一條技巧都用錯方向。

01|Claude Code 的操作模型:Agent,不是 Assistant

大多數人接觸 Claude Code 之前,已經用過某種 AI 工具:ChatGPT、GitHub Copilot、或是 Claude.ai 的對話介面。這些工具建立了一個預設:你問,它答,你決定要不要用。

Claude Code 的設計預設完全不同。

它被設計成一個 Agent,意思是:它會主動採取行動,而不只是回應你的問題。它會自己讀檔案、自己跑指令、自己修改程式碼、自己驗證結果。你給的不是問題,而是任務。它交付的不是答案,而是結果。

這個差別在實際操作上的影響:

  • 當你說「這個 function 有 bug」,Assistant 模型的回應是解釋 bug 在哪、建議怎麼修。Agent 模型的回應是直接去找那個 function、分析 bug、修好它、跑測試確認修好了。
  • 當你說「幫我整理這個專案的文件」,Assistant 模型會給你一份整理建議。Agent 模型會直接去讀所有文件、找出不一致的地方、然後動手修改。

帶錯心態的兩種常見結果:

第一種是低估:只拿 Claude Code 來問問題,從不讓它真正動手。等於買了一個工具只用來當裝飾。

第二種是驚嚇:沒有預期它會直接改檔案,結果它改了一堆東西,你不知道它動了哪裡、為什麼這樣動。

正確的心態是:你是在交派任務給一個會主動執行的工程師,不是在查詢一個知識庫。

02|Context Window 是最重要的資源,不是功能

Claude Code 的所有行為都發生在 context window 裡。它能「記住」的一切——你的指令、它讀過的檔案、它執行過的指令、對話的歷史——全部存在這個有限的空間裡。

Context window 滿了之後會發生什麼:

不是報錯,也不是停止運作。它會繼續運作,但早期的內容會被推出 context,它的「記憶」開始出現缺口。症狀是:

  • 它開始忘記你在 Session 初期說過的限制
  • 它對 codebase 的理解開始出現矛盾
  • 它會重複做已經做過的事,或是忽略已經確認過的決定
  • 回應品質明顯下降,開始出現不確定的猜測

Context window 的實際管理方式:

  • 不要在同一個 Session 裡做太多事(這就是為什麼要每個任務開新 Session)
  • 讓它讀的檔案要精準,不要叫它「讀整個專案」,而是「讀 services/order.pytests/test_order.py
  • 長對話中途如果感覺品質下降,不要繼續撐,開新 Session 重新交代背景

把 context window 想成工作桌面:桌面越亂,工作效率越低。保持桌面整潔是你的責任,不是它的。

03|Session 是工作單位,不是對話單位

這個概念很多人沒有刻意想過,但它決定了你整個工作流程的設計。

Session 在 Claude Code 裡代表什麼:

一個 Session 是一段有開始、有結束的工作上下文。Session 開始時,它的記憶是空的(除了 CLAUDE.md 提供的長期設定)。Session 結束後,對話裡發生的一切不會自動保留到下一個 Session。

Session 不是對話的自然延伸,而是工作的邊界。

正確的 Session 設計原則:

  • 一個 Session 對應一個原子任務
  • Session 的開場要交代清楚:任務是什麼、相關的檔案在哪、完成的標準是什麼
  • Session 的結尾要有一個明確的收尾動作:commit、存檔、或是輸出一份摘要

Session 之間如何傳遞資訊:

不是靠「繼續上次的對話」,而是靠:

  • Git commit(記錄程式碼的當前狀態)
  • CLAUDE.md(記錄長期有效的規則)
  • 你自己寫的任務說明(每個新 Session 的開場白)

04|Claude Code 看到的世界:它能感知什麼、感知不到什麼

Claude Code 不是全知的。它只能感知你明確給它的資訊,加上它自己主動去讀取的資訊。

它能感知的:

  • 你在 Session 裡說的所有內容
  • 它主動讀取的本地檔案(你告訴它路徑,或它自己判斷需要讀)
  • 它執行的指令的輸出(stdout、stderr)
  • CLAUDE.md 的內容
  • 透過 MCP 串接的外部系統

它感知不到的:

  • 你沒有告訴它、它也沒有主動讀取的程式碼
  • 口頭討論過但沒有寫進 CLAUDE.md 的決定
  • 上一個 Session 裡發生的事(除非你重新告訴它)
  • 你的心理預期(「我以為它知道不能動這個檔案」)
  • 程式碼的歷史脈絡(某個設計為什麼這樣做,除非有 comment 或文件)

實際影響:

很多時候 Claude Code 「做錯了」,不是因為它能力不足,而是因為它沒有感知到關鍵資訊。在抱怨它之前,先問自己:「它做這個決定的時候,它知道什麼?它不知道什麼?」

05|它的信心和它的準確率是兩件事

這條是最容易被忽略、但影響最深遠的認知校正。

Claude Code 的回應語氣通常很確定。它說「好的,我已經修好這個 bug 了」的語氣,和「好的,我已經修好這個 bug 了,但我不太確定這樣對不對」的語氣,表面上看起來差不多,但背後的準確率可能差很多。

它為什麼聽起來總是很確定:

它的訓練方式讓它傾向於給出明確的答案,而不是不斷說「我不確定」。這在大多數情況下是有用的,但也意味著:它的語氣不能作為準確率的指標。

具體的風險場景:

  • 它修了一個 bug,說「修好了」,但實際上只修了其中一個觸發路徑
  • 它說「測試全部通過」,但它跑的是舊版的測試,新功能沒有對應的測試
  • 它說「這個設計符合你的架構規範」,但它對你的架構規範的理解是基於不完整的資訊

正確的應對方式:

不是不信任它,而是把驗證步驟系統化,不依賴它的自我評估。讓測試說話,讓 diff 說話,讓你自己的眼睛說話。它的信心是參考,不是保證。


▌2. 指令設計

最重要的核心技能。工具再強,指令寫得不好,輸出就不會好。很多人把 Claude Code 的輸出品質不穩定,歸咎於工具本身,但真正的原因往往在指令下的不好。

01|描述結果,不描述步驟

這是指令設計最根本的原則,也是最難內化的一條。

人在思考一個任務時,自然的方式是想「我要怎麼做」:先做 A,再做 B,然後做 C。把這個思考過程直接寫進指令,就變成了步驟式指令。步驟式指令的問題是:你描述的步驟是你對問題的解法,但你對 codebase 的了解不一定比 Claude Code 深,你選的步驟不一定是最適合的路徑。

步驟式指令 vs 結果式指令的對比:

# 步驟式(低效)
「把 get_user function 改成 async,
 在裡面加上 try-except,
 exception 的時候 return None,
 然後把所有呼叫這個 function 的地方加上 await」

# 結果式(高效)
「get_user 目前在資料庫連線失敗時會讓整個程式 crash。
 我希望它能夠優雅地處理失敗,
 讓呼叫端能夠判斷操作是否成功並決定後續行為。
 請根據現有 codebase 的錯誤處理模式來實作。」

結果式指令讓 Claude Code 根據它對你整個 codebase 的理解來選擇最適合的實作方式。它可能選擇你沒想到的、但更適合你現有架構的解法。

例外情況:

如果你有強烈的技術理由要求特定的實作方式(例如效能限制、團隊規範、外部系統的介面要求),明確指定是對的。結果式指令的精神不是「什麼都不說」,而是「說清楚你真正在意的是什麼」。

02|給約束,不給自由

直覺上你可能覺得給 Claude Code 越多自由度,它能發揮越好。實際上相反:清晰的約束讓它的輸出更準確、更符合你的需求。

自由度高的指令會讓它在大量的可能性空間裡自己做選擇。它做的選擇不一定是你想要的,而且你很難預測它會選什麼。

沒有約束 vs 有約束的對比:

# 沒有約束(危險)
「幫我優化這個資料庫查詢」

# 有約束(安全)
「幫我優化這個資料庫查詢。
 限制條件:
 - 不能改動 schema
 - 不能引入新的 index(需要 DBA 審核)
 - 查詢時間目標是從現在的 800ms 降到 200ms 以下
 - 不能用 raw SQL,維持現有的 ORM 風格」

約束的作用不只是限制它不能做什麼,更重要的是讓它理解你的決策邊界。知道了邊界,它在邊界內的判斷反而更準確。

常見的重要約束類型:

  • 技術限制(不能引入新依賴、不能改 interface、不能動某些檔案)
  • 風格約束(遵循現有的 error handling 模式、命名規範)
  • 範圍約束(只改這個模組、不要動測試以外的地方)
  • 品質約束(改完要通過現有測試、不能降低測試覆蓋率)

03|把驗證標準寫進指令

「做完」的定義如果不寫進指令,它就會用自己的標準來判斷完成。它的標準不一定是你的標準。

沒有驗證標準的指令:

「幫我重構 OrderService」

它做完之後告訴你「完成了」。但完成是什麼意思?程式碼能跑?測試通過?效能改善了?文件更新了?你不知道,它也不確定,雙方各自用不同的標準在溝通。

有驗證標準的指令:

「幫我重構 OrderService。
 完成的標準:
 1. pytest tests/test_order_service.py 全部通過
 2. mypy 對 order_service.py 沒有任何錯誤
 3. 沒有新增任何 public method(只是內部重組)
 4. 每個 private method 都有 docstring 說明它在做什麼
 
 完成後把驗證結果逐條回報給我。」

驗證標準的另一個作用是:讓它在執行過程中自我校準。它知道最終要達到什麼標準,中途發現方向不對時會自己調整,而不是做完一個不符合標準的結果再告訴你。

04|用檔案路徑代替描述

當你需要它理解某段程式碼、某個模組、或某份文件時,給路徑比給描述有效得多。

描述 vs 路徑的對比:

# 描述(低效)
「我有一個處理訂單狀態的 service,
 裡面有幾個方法負責狀態轉換,
 幫我看看有沒有問題」

# 路徑(高效)
「請讀 services/order_state_machine.py,
 分析狀態轉換邏輯,
 找出可能的競爭條件(race condition)」

路徑的好處是精準且無歧義。你的描述和它理解的描述之間有解讀空間,路徑沒有。

進階用法:

# 給多個相關檔案
「請先讀以下檔案再開始:
 - models/order.py(Order 的資料結構)
 - services/order_service.py(業務邏輯)
 - tests/test_order_service.py(現有測試,理解預期行為)
 讀完後告訴我你的理解,確認後再開始修改。」

讓它在開始工作之前先彙報它的理解,是一個有效的校準步驟。你確認它的理解正確之後,後續的工作方向就不容易跑偏。

05|歧義是你的責任,不是它的問題

這條是心態問題,但影響非常實際。

當 Claude Code 的輸出不符合你的預期,第一個反應往往是「它理解錯了」。但更準確的問法是:「我的指令有沒有給它足夠的資訊做出正確的判斷?」

歧義的幾種常見來源:

第一種是術語歧義。你說「優化這個 function」,優化是指效能、可讀性、還是記憶體使用量?你心裡有答案,但指令裡沒有說。

第二種是範圍歧義。你說「修好這個 bug」,修好是指修掉這個特定的觸發路徑,還是找出並修好所有類似的問題?

第三種是優先級歧義。你說「讓這段程式碼更好」,在效能和可讀性之間衝突時,它應該優先哪個?

第四種是隱性前提。你覺得「當然不能動 production config」,但你沒有說,它不知道。

消除歧義的實際方法:

寫完指令之後,用一個簡單的測試:如果一個剛加入團隊、聰明但對這個專案完全不熟悉的工程師收到這個指令,他有沒有足夠的資訊做出正確的判斷?

如果答案是沒有,繼續補充,直到答案是有。


▌3. 工作流程設計

指令設計解決的是「單次任務怎麼說清楚」,工作流程設計解決的是「多個任務怎麼組織」。兩者都重要,但大多數人只想到前者。

01|任務拆解的原則:原子性和可驗證性

任務拆解不是把一件大事切成幾件小事這麼簡單。拆得不好,小任務之間會有隱性依賴、邊界模糊、驗證困難等問題,反而比不拆更麻煩。

好的任務拆解需要同時滿足兩個條件:

原子性:每個子任務是獨立可執行的單位。

原子性的意思是:這個子任務可以在不依賴其他子任務「進行中狀態」的情況下獨立完成。它可以依賴其他任務的「完成結果」(例如上一個任務的 commit),但不能依賴另一個任務「還沒做完的中間狀態」。

可驗證性:每個子任務有明確的完成標準。

完成標準要在任務開始前就定義好,而且要是客觀可檢查的,不能是「看起來差不多了」。

一個實際的拆解範例:

任務:「把現有的同步 API 改成非同步」

錯誤的拆法:

1. 改 controller 層
2. 改 service 層
3. 改 repository 層
4. 更新測試

這個拆法的問題是步驟 1-3 之間有強依賴,controller 改成 async 之後,如果 service 還沒改,整個系統是壞的狀態,無法驗證。

正確的拆法:

1. 在不改現有同步介面的情況下,
   為每個 service method 新增對應的 async 版本
   → 驗證:新的 async method 有獨立測試且通過

2. 逐一把 controller 切換到呼叫 async 版本
   → 驗證:每切換一個 endpoint,對應的整合測試通過

3. 確認所有 controller 都切換完畢後,
   移除舊的同步版本
   → 驗證:沒有任何程式碼呼叫舊版 method,測試全過

這個拆法的每個步驟結束時,系統都處於可運作的狀態,可以獨立驗證。

02|Session 邊界的設計

知道「一個 Session 做一件事」之後,下一個問題是:一件事的邊界在哪裡?

Session 邊界的設計有三個判斷標準:

第一:這個任務完成後,有沒有一個自然的 checkpoint?

Checkpoint 是指一個可以獨立存在的穩定狀態,例如一個可以 commit 的程式碼狀態、一份可以獨立閱讀的文件、一個通過的測試套件。如果沒有自然的 checkpoint,可能是任務還需要繼續拆。

第二:這個任務需要多少背景資訊?

如果一個任務需要讀取超過 10-15 個檔案才能開始工作,context 很快會被資訊佔滿,工作品質會在中途下降。這是訊號:任務範圍太大,或是需要先把相關的背景資訊整理進 CLAUDE.md,再以精簡的方式在 Session 裡引用。

第三:這個任務的輸出會成為下一個任務的輸入嗎?

如果是,兩個任務應該是不同的 Session,中間靠 git commit 或文件傳遞狀態。如果不是,考慮它們是否真的是獨立的任務,還是同一個任務的兩個部分。

Session 開場的標準格式:

一個好的 Session 開場應該包含:

任務:[一句話說明這個 Session 要完成什麼]
相關檔案:[列出需要讀取的檔案路徑]
背景:[任何不在程式碼裡但必要的上下文]
完成標準:[客觀可驗證的完成條件]
限制:[不能做的事]

不需要每次都用這個格式,但這五個元素都應該在開場時涵蓋到。

03|Git 是 Session 之間唯一可靠的記憶

這條值得單獨強調,因為很多人低估了它的重要性。

Claude Code 沒有跨 Session 的記憶。CLAUDE.md 提供的是規則和偏好,不是狀態。真正能可靠地記錄「現在的 codebase 是什麼狀態」的,只有 git。

這個認知帶來的具體實踐:

勤 commit,而且 commit message 要有意義。

不是做完一個大功能才 commit,而是每完成一個可驗證的子任務就 commit。Claude Code 在新 Session 裡可以透過 git loggit diff 理解最近發生了什麼,但它能理解的品質完全取決於你的 commit message 品質。

好的 commit message 對 Claude Code 的意義:

# 對 Claude Code 幾乎沒用的 commit message
「update order service」

# 對 Claude Code 有用的 commit message
「refactor(order): 將狀態轉換邏輯抽取到 OrderStateMachine

原本散落在 OrderService 各個 method 裡的狀態轉換判斷,
統一移到新的 OrderStateMachine class。
OrderService 的 public interface 維持不變。
相關測試在 tests/test_order_state_machine.py」

第二種 commit message 讓下一個 Session 的 Claude Code 在讀 git log 時,能夠精確理解這個改動的意圖、範圍、和影響,不需要重新讀完所有改動的程式碼。

用 git 做安全網:

在開始任何較大的修改之前,確保當前狀態有一個乾淨的 commit。這樣無論 Claude Code 在中途做了什麼奇怪的事,你都有一個確定可以回到的點。

04|Worktree 讓並行成為可能

當你有多個獨立任務需要同時進行,Worktree 是讓它們真正互不干擾的機制。

Worktree 的核心概念:

Git Worktree 讓你在同一個 repository 裡,同時 checkout 多個 branch 到不同的目錄。每個目錄是完全獨立的工作環境,有自己的工作區狀態,不會互相污染。

# 主目錄:main branch,日常工作
~/projects/myapp/

# 建立第二個 worktree 給 feature 開發
git worktree add ~/projects/myapp-feature feature/async-refactor

# 建立第三個 worktree 給緊急 hotfix
git worktree add ~/projects/myapp-hotfix hotfix/payment-crash

# 查看所有 worktree 狀態
git worktree list

之後在三個不同的終端機視窗,分別在三個目錄下啟動 Claude Code,三個 Session 完全隔離,各自處理自己的任務。

Worktree 特別適合的場景:

  • 讓 Claude Code 在 feature branch 跑一個耗時的重構任務,同時你繼續在 main branch 做其他工作
  • 同時有 hotfix 和 feature 開發進行,需要在不同 branch 之間快速切換而不影響彼此
  • 需要在不同的實驗性方向之間比較結果,各自跑完再決定採用哪個

Worktree 的注意事項:

同一個 branch 不能同時被兩個 worktree checkout。如果你需要在同一個 branch 上開兩個 Session,考慮改成用兩個不同的 branch,最後再 merge。

05|Human-in-the-loop 不是效率問題,是風險管理

很多人把 Human-in-the-loop(人工確認節點)理解成「不信任 AI 所以要一直監視它」。這個理解是錯的。

正確的理解是:在任務流程的關鍵節點,有些判斷需要業務知識或風險承擔能力,這些判斷只有人能做。

哪些節點應該設置人工確認:

第一類:不可逆操作前。

刪除資料、修改 migration、改動對外的 API interface、部署到生產環境。這些操作一旦執行,rollback 的成本很高。在這些操作前停下來讓人確認,不是不信任 Claude Code,而是承認這個決定的後果需要人來承擔。

第二類:方向性判斷點。

當任務進行到一個岔路口,兩個方向都技術上可行,但選擇哪個取決於業務優先級或團隊偏好。Claude Code 可以列出選項和各自的取捨,但決定應該由你來做。

第三類:跨越模組邊界時。

當一個任務的修改從一個模組延伸到另一個模組,確認這個延伸是你預期的,而不是它自己判斷需要這樣做的。

在指令裡設置確認節點的寫法:

請依序執行以下步驟。
每個步驟完成後停下來,
等我明確說「繼續」才執行下一步。

步驟 1:讀取所有相關檔案,
        列出你的修改計畫和預計影響的範圍。
        (我會在這裡確認計畫是否正確)

步驟 2:執行修改。
        (我會在這裡確認 diff 是否符合預期)

步驟 3:跑測試並回報結果。
        (我會在這裡決定是否需要補充測試)

步驟 4:生成 commit message,等我確認後再 commit。

這個寫法讓流程有結構、有節奏,不是隨機暫停,而是在有意義的點讓人介入。


▌4. 長期記憶系統

Claude Code 沒有跨 Session 的自動記憶。但這不代表你每次都要從零開始交代背景。長期記憶系統的設計,就是把「需要每次重複說的事」沉澱成結構化的文件,讓 Claude Code 在每個 Session 開始時自動載入。

設計得好,新 Session 的開場白可以很短。設計得不好,你會發現自己每次都在重複解釋同樣的事。

01|CLAUDE.md 的三層結構:專案、模組、個人

CLAUDE.md 不是一個單一的檔案,而是一個階層式的設定系統。Claude Code 會根據當前工作目錄,自動合併讀取不同層級的 CLAUDE.md。

三層結構:

~/                          # 第一層:個人全域設定
└── CLAUDE.md               # 適用於所有專案的個人偏好

~/projects/myapp/           # 第二層:專案層級
└── CLAUDE.md               # 這個專案的架構、規範、限制

~/projects/myapp/
├── services/
│   └── CLAUDE.md           # 第三層:模組層級
└── tests/
    └── CLAUDE.md           # 測試目錄的特定規範

各層應該放什麼:

個人全域層(~/CLAUDE.md):

放你在所有專案裡都適用的個人偏好,例如:

## 溝通偏好
- 每個步驟完成後主動回報結果,不要等我問
- 不確定時先列出選項和各自的取捨,不要自己猜測決定
- 錯誤訊息貼完整,不要自己節錄

## 程式碼風格偏好
- 函式命名用動詞開頭
- 優先可讀性,在沒有明確效能需求的情況下不要過度優化
- 每個 public function 都要有 type hint

專案層(~/projects/myapp/CLAUDE.md):

放這個專案特有的資訊:

## 專案概述
訂單管理系統,處理電商平台的訂單生命週期。
Tech stack:FastAPI + PostgreSQL + Redis + Celery

## 目錄結構
- models/:SQLAlchemy ORM models
- services/:業務邏輯,不直接操作資料庫
- repositories/:所有資料庫操作集中在這裡
- api/:FastAPI route handlers,只做輸入驗證和呼叫 service

## 架構規則
- Service 層不能直接 import SQLAlchemy session,必須透過 repository
- API 層不能包含業務邏輯,業務邏輯一律在 service 層
- 不使用全域狀態,所有依賴透過 dependency injection 傳入

## 禁止事項
- 不直接修改 alembic migration 檔案
- 不動 config/production.yaml
- 不在沒有對應測試的情況下新增 public method

模組層(~/projects/myapp/services/CLAUDE.md):

放這個模組特有的細節:

## Services 模組規範
- 每個 service class 對應一個業務領域
- Method 命名:動詞 + 名詞(create_order, cancel_order)
- 所有 service method 都要有對應的 unit test
- 錯誤處理:業務邏輯錯誤用自訂 Exception,
  系統錯誤讓它往上傳播

三層合併的效果:

Claude Code 在 services/ 目錄工作時,會同時讀取個人全域層、專案層、和 services 模組層的設定,三層規則同時生效,不需要你在每個 Session 裡重複說明。

02|什麼該寫進 CLAUDE.md,什麼不該

CLAUDE.md 寫太少,每個 Session 都要重複交代背景。寫太多,關鍵資訊被稀釋,Claude Code 反而抓不到重點。

應該寫進 CLAUDE.md 的:

  • 長期穩定的規則:不常改變的架構決策、命名規範、禁止事項
  • 非顯而易見的約束:外部系統的特殊行為、歷史遺留的技術債說明、某個設計看起來奇怪但有原因的地方
  • 溝通協議:你希望它在不確定時怎麼做、驗證的標準格式、回報的方式
  • 工具和環境資訊:測試指令、build 指令、常用的 debug 工具

不應該寫進 CLAUDE.md 的:

  • 任務特定的細節:某次特定任務的背景,寫進去之後對其他任務沒有意義,反而佔用空間
  • 會頻繁改變的資訊:如果某條規則每週都在變,它不適合放在 CLAUDE.md,應該在每個 Session 開場時說明
  • 可以從程式碼直接讀出來的資訊:如果 Claude Code 讀一下 models/order.py 就能理解 Order 的結構,不需要在 CLAUDE.md 裡重複描述
  • 過長的背景故事:CLAUDE.md 超過 500 行之後,效果會開始遞減。資訊量越大,每條規則被注意到的機率越低

一個實用的判斷標準:

問自己:「這條資訊,是不是每一個在這個專案工作的 Session 都需要知道?」如果是,寫進 CLAUDE.md。如果只是某些 Session 需要,在那個 Session 的開場時說就好。

03|Skill 的參數化設計

Skill 是把可重複使用的工作流程封裝成模板。但 Skill 設計得不好,會變成一個太死板、無法適應不同情境的硬編碼流程。

參數化的概念:

好的 Skill 有明確的「輸入變數」,呼叫時填入具體的值,Skill 的流程根據這些值調整行為。

一個設計良好的參數化 Skill:

# Skill: 新增 Repository Method

## 輸入參數
- ENTITY:操作的資料實體(例如 Order、User、Payment)
- METHOD_TYPE:操作類型(get_by_id / list_by_filter / create / update / delete)
- SPECIAL_REQUIREMENTS:任何特殊需求(選填)

## 執行步驟

### 步驟 1:理解現有模式
讀取 repositories/{ENTITY}Repository.py,
分析現有 method 的命名風格、參數格式、回傳型別、錯誤處理方式。
如果檔案不存在,讀取其他任意一個 Repository 作為參考。

### 步驟 2:實作 method
根據 METHOD_TYPE 和現有模式實作新的 method。
- get_by_id:回傳 Optional[{ENTITY}]
- list_by_filter:回傳 List[{ENTITY}],支援分頁
- create:回傳新建的 {ENTITY},失敗時 raise RepositoryError
- update:回傳更新後的 {ENTITY},找不到時 raise NotFoundError
- delete:回傳 bool,找不到時 raise NotFoundError

如果有 SPECIAL_REQUIREMENTS,在符合以上規範的前提下處理。

### 步驟 3:補充測試
在 tests/repositories/test_{ENTITY}Repository.py 新增對應測試。
至少涵蓋:正常流程、資料不存在的情況、資料庫錯誤的情況。

### 步驟 4:驗證
執行 pytest tests/repositories/test_{ENTITY}Repository.py
回報結果。

## 禁止事項
- 不在 Repository 裡包含業務邏輯
- 不直接操作其他 Entity 的 table(跨 entity 操作透過 transaction service)

呼叫這個 Skill 的方式:

使用「新增 Repository Method」Skill:
ENTITY = Order
METHOD_TYPE = list_by_filter
SPECIAL_REQUIREMENTS = 需要支援按日期範圍和狀態複合篩選,
                       且結果要支援游標分頁(cursor-based pagination)

這樣的設計讓同一個 Skill 可以適應不同的 entity 和操作類型,不需要為每個情境寫一個獨立的 Skill。

Skill 的組織方式:

.claude/
└── skills/
    ├── add-repository-method.md
    ├── add-api-endpoint.md
    ├── code-review.md
    ├── write-migration.md
    └── debug-production-issue.md

每個 Skill 一個檔案,檔名清楚說明用途。需要使用時:

載入 .claude/skills/add-api-endpoint.md,
執行這個 Skill,參數如下:...

04|設定檔納入版本控制的團隊意義

這不只是個人的好習慣,而是影響整個團隊協作品質的實踐。

應該 commit 的設定檔:

專案根目錄/
├── CLAUDE.md                    # 必須 commit
└── .claude/
    ├── config.json              # 專案層級設定,必須 commit
    └── skills/                  # 所有 Skill 檔案,必須 commit
        ├── add-endpoint.md
        ├── code-review.md
        └── write-migration.md

不應該 commit 的設定檔:

~/.claude/config.json            # 個人全域設定,不 commit
.claude/config.local.json        # 個人本地覆寫,加入 .gitignore

為什麼這對團隊很重要:

當 CLAUDE.md 和 Skill 檔案納入版本控制,整個團隊的 Claude Code 行為會趨於一致。新成員 clone 專案之後,Claude Code 就知道這個專案的架構規則、命名規範、禁止事項,不需要靠口耳相傳。

更重要的是:設定檔的演進歷史本身就是決策歷史。 當你在 CLAUDE.md 裡加了一條「不直接修改 migration 檔案」,commit message 應該說明為什麼,例如:

docs(claude): 禁止直接修改 migration 檔案

過去兩次事故都是因為手動修改了已部署的 migration,
導致 schema 和 migration 歷史不一致。
所有 migration 修改必須透過 alembic revision 指令新增。

六個月後新成員看到這條規則,能理解背後的原因,而不只是看到一條沒有脈絡的禁令。

05|記憶系統的維護:什麼時候該更新

CLAUDE.md 和 Skill 不是寫完就不動的文件。它們需要隨著專案演進而更新,否則會從「有用的指引」變成「過時的誤導」。

觸發更新的訊號:

第一個訊號:你在 Session 裡重複說了三次以上的同一件事。

如果你發現自己在不同的 Session 裡一直說「對了,我們不用 ORM,直接用 raw SQL」,這條資訊應該進 CLAUDE.md,而不是繼續靠你記得說。

第二個訊號:Claude Code 做了一個「它不應該這樣做」的決定。

當 Claude Code 的判斷和你的預期不符,有兩種可能:一是指令不清楚,二是 CLAUDE.md 沒有涵蓋這個情境。釐清之後,把缺少的規則補進去。

第三個訊號:技術規範改變了。

換了測試框架、改了 error handling 的模式、引入了新的架構層。對應的 CLAUDE.md 和 Skill 要同步更新,否則它會照舊規範做事。

第四個訊號:某個 Skill 的輸出品質開始下降。

Skill 寫好之後不是永遠有效的。當 codebase 的結構改變,Skill 裡描述的路徑、模式、規範可能已經過時。定期回頭檢查 Skill 的輸出,確認它還在產生符合預期的結果。

維護的實際做法:

不需要定期排程「更新 CLAUDE.md」,而是把更新變成工作流程的一部分:

每個 Sprint 結束時,花 10 分鐘回顧:
- 這個 Sprint 裡有沒有重複說過的事?→ 補進 CLAUDE.md
- 有沒有 Claude Code 做了不符預期的決定?→ 補對應的規則
- 有沒有技術決策改變?→ 更新對應的規則和 Skill

把這個回顧變成習慣,CLAUDE.md 就會隨著專案成長而越來越準確,而不是越來越過時。


▌5. 驗證與品質控制

這可能是本文中,對實際產品品質影響最大的部分。

很多人把 Claude Code 的工作流程設計成:下指令 → 它執行 → 接受輸出。

這個流程在任務簡單、風險低的情況下沒問題。但當任務複雜度提高,沒有系統性的驗證機制,錯誤會在你不知情的情況下累積,直到某個時間點集中爆發。

01|「完成」的定義要在任務開始前就說清楚

這條看起來像是第二章指令設計的重複,但角度不同。指令設計章節說的是「怎麼寫出好指令」,這裡說的是「為什麼驗證標準必須是指令的一部分,而不是事後補充的要求」。

沒有預先定義完成標準會發生什麼:

Claude Code 完成任務後,它會用自己的標準評估「完成了沒有」。它的標準通常是:程式碼語法正確、邏輯看起來合理、如果有跑測試的話測試通過。

這個標準和你的標準之間可能有很大的落差:

  • 你的標準可能包含效能要求,它的標準沒有
  • 你的標準可能包含某個特定的錯誤處理模式,它用了不同但技術上正確的模式
  • 你的標準可能包含文件更新,它認為「程式碼改好了就完成了」

事後提出額外要求的成本:

當你在它說「完成了」之後才說「對了,還需要更新文件」,它需要重新理解當前狀態,可能已經消耗了大量 context,回應品質開始下降。更糟的是,它可能在「補文件」的過程中對已有的程式碼做出不必要的小調整,引入新的問題。

預先定義完成標準的模板:

任務完成的標準(全部達到才算完成):

程式碼層面:
□ pytest tests/[相關測試檔案] 全部通過
□ mypy [相關檔案] 無錯誤
□ ruff check [相關檔案] 無警告

功能層面:
□ [具體的功能行為描述]
□ [邊界案例的處理方式]

文件層面:
□ docstring 更新(如果 public interface 有改變)
□ CHANGELOG.md 新增對應項目

完成後請逐條確認以上標準,
並回報每一條的驗證結果。

把這個模板存進你的 Skill 系統,每個任務開始前調整具體的內容,而不是每次從頭想。

02|測試先行的工作流程

測試先行(Test First)在 Claude Code 的語境下,意義和傳統 TDD 略有不同。傳統 TDD 強調的是設計驅動,Claude Code 語境下的測試先行強調的是驗證標準的具象化。

在開始實作之前先寫測試,等於是把你對「正確行為」的理解,轉化成機器可以執行的規格。這個規格比自然語言描述更精確,也更難有歧義。

具體的工作流程:

# 第一步:描述行為,讓它寫測試
「針對 PaymentService.process_refund,先寫測試。
 不要實作,只寫測試。

 需要涵蓋的情境:
 1. 正常退款成功:金額正確退回,訂單狀態更新為 refunded
 2. 金額超過原始訂單金額:應拋出 InvalidRefundAmountError
 3. 訂單狀態不允許退款(例如已是 refunded):
    應拋出 InvalidOrderStateError
 4. 金融系統呼叫失敗:應拋出 PaymentGatewayError,
    且不應改變訂單狀態(確認原子性)
 5. 部分退款:金額小於原始金額,狀態更新為 partially_refunded

 寫完之後跑測試,確認全部是紅燈(失敗)再告訴我。」
# 第二步:確認測試邏輯正確後,再開始實作
「測試邏輯正確。
 現在實作 process_refund,目標是讓所有測試通過。
 不要修改測試,只實作 method。」
# 第三步:測試全過後,考慮重構
「所有測試通過了。
 在不改變任何測試的前提下,
 看看實作有沒有可以整理的地方。
 如果有,說明你打算怎麼整理,等我確認再動。」

這個流程的關鍵價值:

測試是你和 Claude Code 之間對「正確」的共同定義。一旦測試寫好並確認邏輯正確,後續的實作和重構都有一個客觀的驗證標準,不需要靠你的主觀判斷來評估「改得對不對」。

03|讓它 review 自己的輸出

在你接受任何輸出之前,讓 Claude Code 先對自己的工作做一次批判性的審查。

這聽起來像是讓它自己給自己打分數,效果有限。但實際上,生成輸出和審查輸出是兩個不同的認知過程。它在生成時做的是建構,在審查時做的是批判。兩個過程會發現不同的問題。

有效的自我 review 指令:

你剛才完成了 process_refund 的實作。
在我 review 之前,請你先做一次批判性的自我審查。

審查重點:
1. 情境 4(金融系統失敗)的原子性是否真的保證了?
   如果在扣款成功但更新訂單狀態失敗的情況下,
   系統的狀態是什麼?
2. 所有的自訂 Exception 是否都有足夠的資訊讓呼叫端判斷如何處理?
3. 有沒有任何你在實作時做了假設但沒有明確說出來的地方?
4. 測試有沒有測到你實際實作的邏輯,
   還是測試在測一個你以為會發生但實際上不會發生的情境?

誠實地回報你發現的問題,即使需要修改你剛才的輸出。

這個步驟最有價值的地方:

第四點——讓它審查測試本身是否真的在測它實作的邏輯——是人工 review 最容易忽略的盲點。測試通過了,但測試測的是錯誤的東西,這種情況比你想像的更常見。

04|讓它 review 你的輸出

反過來,你自己寫的程式碼,也可以讓 Claude Code 做 review。這不是因為你比它差,而是因為它有幾個人工 review 做不到的優勢。

Claude Code 做 code review 的獨特優勢:

  • 不會因為看太久而麻木:人工 review 大量 diff 時,注意力會下降,後面的內容容易漏看。它不會。
  • 對 pattern 的記憶比人強:它可以同時記住整個 codebase 的命名規範、架構模式,不會因為「這個 reviewer 不熟這個模組」而漏掉不一致的地方。
  • 沒有社交顧慮:它不會因為這是資深工程師寫的就不敢指出問題。

設計有效的 review 指令:

請 review 以下 git diff。
這是我自己寫的程式碼,不是你生成的。

重點檢查以下項目,每項都要明確回答有沒有問題:

1. 邏輯正確性
   - 是否有邏輯錯誤或遺漏的 edge case?

2. 錯誤處理
   - 所有可能的失敗路徑是否都有處理?
   - Exception 的型別和訊息是否足夠讓呼叫端判斷?

3. 架構一致性(參照 CLAUDE.md 的規範)
   - 是否有違反分層架構的地方?
   - 命名是否符合現有規範?

4. 測試品質
   - 測試是否真的在測實作邏輯?
   - 有沒有明顯缺少的測試案例?

5. 你個人認為最值得關注的一個問題
   (不限於以上四類)

[貼上 git diff]

讓 review 更有針對性:

你對自己的弱點比 Claude Code 更了解。如果你知道自己習慣性地忽略 null check、或是容易在 async 程式碼裡製造 race condition,把這些加進 review checklist,讓它特別關注你的盲點。

05|什麼程度的輸出值得信任,什麼程度需要人工複查

不是所有 Claude Code 的輸出都需要同樣強度的驗證。把驗證資源集中在高風險的輸出,低風險的輸出用輕量的方式確認,是有效率的品質控制。

風險分級的判斷框架:

低風險輸出(輕量驗證即可):

  • 新增的功能有完整的測試覆蓋,且測試是先於實作寫好的
  • 修改範圍完全在一個獨立模組內,沒有跨越模組邊界
  • 有明確的 rollback 機制(例如 feature flag)
  • 修改的是新建的程式碼,不是改動現有的邏輯

輕量驗證:跑測試、快速掃一遍 diff、確認沒有明顯的問題。

中風險輸出(需要仔細 review):

  • 修改了現有的業務邏輯
  • 跨越了模組邊界,影響到多個地方
  • 改動了 public interface(即使是向後相容的改動)
  • 涉及並發、快取、或狀態管理的程式碼

仔細 review:逐行看 diff、確認測試覆蓋了所有修改路徑、讓它做自我 review。

高風險輸出(必須人工深度審查):

  • 涉及認證、授權、資料加密的程式碼
  • 資料庫 migration
  • 改動了對外的 API contract
  • 涉及金融交易或不可逆操作的邏輯
  • 效能關鍵路徑的修改

深度審查:除了以上所有步驟,還需要在 staging 環境實際驗證、考慮讓另一個人也 review、可能需要額外的壓力測試或安全審查。

一個容易忽略的原則:

當你不確定某個輸出的風險等級時,預設往高一級處理。驗證的成本遠低於修復生產環境問題的成本。


▌6. 工具整合

工具整合是 Claude Code 能力邊界的延伸機制。原生的 Claude Code 能做的事是有限的:讀寫本地檔案、執行 bash 指令、理解程式碼。透過 MCP(Model Context Protocol),這個邊界可以向外擴展到幾乎任何系統。

但工具整合也是最容易踩坑的領域。整合得好,Claude Code 的能力成倍放大。整合得不好,複雜度上升、debug 困難、而且問題出現時很難定位根源。

01|MCP 的正確心智模型:能力擴充,不是魔法

很多人第一次接觸 MCP 時,把它理解成「讓 Claude Code 連上外部服務」。這個理解方向對,但不夠精確,容易產生錯誤的預期。

更精確的理解:

MCP 是一個標準化的介面協議,讓 Claude Code 能夠呼叫外部定義的工具(tools)。每個 MCP server 對外暴露一組工具,每個工具有明確的名稱、描述、和參數格式。Claude Code 在執行任務時,會根據工具的描述判斷什麼時候需要呼叫哪個工具。

這個架構有幾個重要的含意:

第一,Claude Code 的判斷基於工具的描述,不是工具的實作。

如果一個工具的描述寫得不清楚,Claude Code 可能在不適當的時機呼叫它,或是在應該呼叫時沒有呼叫。工具描述的品質直接影響整合效果。

第二,MCP 只是橋樑,外部系統的限制仍然存在。

Claude Code 透過 MCP 呼叫 Linear API,並不代表它突破了 Linear API 的 rate limit、權限控制、或資料範圍限制。外部系統的所有約束依然適用。

第三,工具呼叫是同步的,會佔用 context。

每次工具呼叫的請求和回應都會佔用 context window 的空間。如果一個任務需要大量的工具呼叫,context 消耗會比純文字任務快得多。

正確的預期設定:

MCP 讓 Claude Code 獲得它原本沒有的能力,但這些能力的邊界由外部系統決定,不是由 Claude Code 決定。整合之後,Claude Code 能做的事是「在它的判斷能力範圍內,操作外部系統能做的事」,兩者的交集,不是兩者的聯集。

02|選現成 MCP 還是自建的判斷標準

面對一個整合需求,第一個決策點是:用現成的開源 MCP server,還是自己建一個?

優先選現成 MCP 的情況:

  • 目標服務是主流工具(GitHub、Linear、Notion、Slack 等),社群已有維護良好的 MCP server
  • 你需要的功能是該服務的標準操作,現成 MCP 已經涵蓋
  • 團隊沒有維護自建 MCP server 的資源

現成 MCP 的主要優點是零開發成本、有社群維護、文件相對完整。缺點是你無法完全控制它的行為,遇到問題時 debug 路徑更長。

應該自建 MCP 的情況:

  • 目標是公司內部系統,沒有現成的 MCP server
  • 現成 MCP server 的功能不符合需求,需要客製化
  • 需要整合多個內部系統,自建一個統一的 MCP server 比串接多個獨立的更合理
  • 對安全性有嚴格要求,不希望透過第三方 MCP server 傳遞敏感資料

一個常被忽略的中間選項:

在決定自建之前,先評估能不能用 bash 工具解決。很多「需要 MCP 整合」的需求,實際上可以靠一個簡單的 shell script 加上 Claude Code 的 bash 執行能力來完成,不需要完整的 MCP server 架構。

# 例如,查詢內部 API 不一定需要 MCP
# 一個簡單的 wrapper script 就夠了
#!/bin/bash
# internal_api.sh
curl -H "Authorization: Bearer $INTERNAL_API_TOKEN" \
     "https://internal.company.com/api/$1"

然後在 CLAUDE.md 裡說明這個 script 的存在和用途,Claude Code 就可以在需要時直接呼叫它。這個方案的維護成本遠低於完整的 MCP server。

03|自建 MCP 的最小可行設計

如果評估後確定需要自建,從最小可行的範圍開始,不要一開始就設計大而全的系統。

最小可行 MCP server 的設計原則:

只做唯讀操作。

第一版只實作查詢類的工具(get、list、search),不實作寫入操作。唯讀操作的風險極低,可以快速驗證整合是否正常運作。寫入操作在確認整合穩定之後再加。

每個工具只做一件事。

不要設計「萬能工具」,例如「操作工單系統」這樣的工具太寬泛。應該是「get_ticket」、「list_tickets_by_assignee」、「search_tickets_by_keyword」這樣的細粒度工具。細粒度的工具讓 Claude Code 更容易判斷什麼時候用哪個,也讓 debug 更容易。

工具描述比實作更重要。

每個工具的 description 要精確說明:這個工具做什麼、什麼時候應該用它、回傳的資料格式是什麼。Claude Code 完全依賴這個描述來判斷是否呼叫這個工具。

一個實際的最小可行範例(Python):

from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp import types
import httpx
import os

server = Server("company-internal-v1")

@server.list_tools()
async def list_tools():
    return [
        types.Tool(
            name="get_ticket",
            description="""從公司內部工單系統取得單一 ticket 的詳細資訊。
            使用時機:當需要了解特定 ticket 的內容、狀態、負責人時。
            回傳:ticket 的標題、描述、狀態、負責人、建立時間。
            注意:只能查詢你有權限存取的 ticket。""",
            inputSchema={
                "type": "object",
                "properties": {
                    "ticket_id": {
                        "type": "string",
                        "description": "Ticket ID,格式為 PROJ-1234"
                    }
                },
                "required": ["ticket_id"]
            }
        ),
        types.Tool(
            name="list_my_open_tickets",
            description="""列出目前指派給當前使用者的所有未完成 ticket。
            使用時機:需要了解目前有哪些待處理工作時。
            回傳:ticket 列表,每個包含 id、標題、優先級、最後更新時間。
            限制:最多回傳 50 筆,按優先級排序。""",
            inputSchema={
                "type": "object",
                "properties": {},
                "required": []
            }
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict):
    token = os.environ.get("INTERNAL_API_TOKEN")
    if not token:
        raise ValueError("INTERNAL_API_TOKEN 環境變數未設定")

    async with httpx.AsyncClient() as client:
        if name == "get_ticket":
            ticket_id = arguments["ticket_id"]
            response = await client.get(
                f"https://internal.company.com/api/tickets/{ticket_id}",
                headers={"Authorization": f"Bearer {token}"},
                timeout=10.0
            )
            response.raise_for_status()
            data = response.json()
            # 只回傳必要欄位,不回傳整個 API response
            result = {
                "id": data["id"],
                "title": data["title"],
                "description": data["description"],
                "status": data["status"],
                "assignee": data["assignee"]["name"],
                "created_at": data["created_at"]
            }
            return [types.TextContent(
                type="text",
                text=str(result)
            )]

        elif name == "list_my_open_tickets":
            response = await client.get(
                "https://internal.company.com/api/tickets",
                params={"assignee": "me", "status": "open", "limit": 50},
                headers={"Authorization": f"Bearer {token}"},
                timeout=10.0
            )
            response.raise_for_status()
            data = response.json()
            tickets = [
                {
                    "id": t["id"],
                    "title": t["title"],
                    "priority": t["priority"],
                    "updated_at": t["updated_at"]
                }
                for t in data["tickets"]
            ]
            return [types.TextContent(
                type="text",
                text=str(tickets)
            )]

async def main():
    async with stdio_server() as streams:
        await server.run(
            *streams,
            server.create_initialization_options()
        )

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

幾個實作細節值得注意:

只回傳必要欄位,不把整個 API response 丟給 Claude Code。API response 往往包含大量 Claude Code 不需要的欄位,全部回傳會不必要地佔用 context window。

明確設定 timeout,避免外部 API 沒有回應時整個工具呼叫無限等待。

認證資訊一律從環境變數讀取,不在程式碼裡 hardcode,也不在 MCP server 的設定檔裡明文儲存。

04|MCP 的 debug 方法論

MCP 整合出問題時,錯誤可能發生在四個不同的層:Claude Code 的判斷層、MCP 協議層、MCP server 的實作層、外部系統層。有系統的 debug 方法論可以大幅縮短定位問題的時間。

逐層排查的流程:

第一層:確認 MCP server 是否正常啟動。

# 直接執行 MCP server,看有沒有啟動錯誤
python your_mcp_server.py

# 或檢查 Claude Code 的 MCP 連線狀態
# 在 Claude Code 裡執行:
/mcp

如果 MCP server 連不上,後面的層都不需要看。

第二層:確認工具列表是否正確載入。

# 在 Claude Code Session 裡問它:
「列出你目前可以使用的所有工具,包括來自 MCP 的工具。」

如果它列不出你期望的工具,問題在 MCP server 的 list_tools 實作,或是 Claude Code 的 MCP 設定檔。

第三層:獨立測試工具呼叫。

# 要求 Claude Code 直接呼叫特定工具,
# 不要讓它自己判斷要不要呼叫:
「請直接呼叫 get_ticket 工具,ticket_id 是 PROJ-1234,
 不管結果如何都回報給我。」

如果工具呼叫失敗,看錯誤訊息判斷是 MCP server 的問題還是外部 API 的問題。

第四層:直接測試外部 API。

# 繞過 MCP,直接呼叫外部 API
curl -H "Authorization: Bearer $INTERNAL_API_TOKEN" \
     "https://internal.company.com/api/tickets/PROJ-1234"

如果直接呼叫也失敗,問題在外部系統(認證、權限、API 本身的問題),和 Claude Code 無關。

一個容易忽略的 debug 工具:

在 MCP server 的實作裡加上詳細的 logging,把每次工具呼叫的請求參數和回應都記錄下來。這在 debug 時可以讓你精確看到 Claude Code 傳了什麼參數、外部 API 回傳了什麼,不需要靠猜測。

import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

@server.call_tool()
async def call_tool(name: str, arguments: dict):
    logger.debug(f"工具呼叫:{name},參數:{arguments}")
    # ... 實作 ...
    logger.debug(f"工具回應:{result}")
    return result

05|工具鏈的複雜度管理

隨著整合的工具越來越多,複雜度會快速上升。工具鏈的複雜度管理是一個長期問題,不是一次性的設計決策。

複雜度上升的警訊:

  • Claude Code 開始呼叫錯誤的工具(把本來應該用工具 A 的情境用了工具 B)
  • 工具呼叫的頻率異常高,消耗大量 context
  • 某個任務失敗了,但你不確定是哪個工具出了問題
  • 新加入的工具和現有工具的描述有重疊,讓 Claude Code 難以判斷要用哪個

管理複雜度的實際做法:

工具數量控制。

每個 MCP server 的工具數量建議不超過 10-15 個。超過這個數量,Claude Code 在選擇工具時的準確率會開始下降。如果工具很多,考慮按功能領域拆成多個 MCP server,在 CLAUDE.md 裡說明每個 server 負責的範疇。

工具描述的唯一性原則。

每個工具的描述要能讓 Claude Code 清楚區分它和其他工具的差別。如果兩個工具的描述讀起來很像,要明確在描述裡說明它們的差異和各自的適用情境。

# 容易混淆的描述(不好)
get_order:取得訂單資訊
get_order_detail:取得訂單詳細資訊

# 清楚區分的描述(好)
get_order:取得訂單的摘要資訊(id、狀態、總金額、建立時間)。
           適用於需要快速確認訂單狀態、不需要明細的情況。
           
get_order_detail:取得訂單的完整資訊,包含所有商品明細、
                  物流資訊、付款紀錄、歷史狀態變更。
                  適用於需要深入分析特定訂單的情況。
                  注意:回傳資料量較大,只在必要時使用。

定期審查工具使用情況。

每隔一段時間,回顧一下哪些工具被頻繁使用、哪些幾乎從來不被用。幾乎不被用的工具可能是描述寫得不好(讓 Claude Code 不知道什麼時候用它),也可能是這個工具的需求本來就很少,可以考慮移除。


▌7. 常見失敗模式

前面六項主題,討論的是「怎麼做對」。本主題討論的是「為什麼會做錯」。

失敗模式和踩坑清單的差別在於:踩坑清單告訴你「不要做 X」,失敗模式分析告訴你「為什麼你會自然而然地走向 X、X 會怎麼讓你付出代價、以及怎麼建立系統來避免它」。

知道不該做什麼不夠,你需要理解失敗的機制,才能在它發生之前認出它。

01|速度陷阱:跑得快但方向錯

這是使用 Claude Code 之後最常見的第一個失敗模式,而且通常要等到損失已經造成才會被發現。

陷阱的機制:

Claude Code 的執行速度很快。一個任務可能在幾分鐘內就有大量的輸出:程式碼修改、測試、文件。這個速度會產生一種「進展感」,讓你覺得事情在快速往前走。

問題是,速度和方向是兩個獨立的維度。你可以很快地朝錯誤的方向前進。

速度陷阱的具體場景:

你給了一個描述不夠精確的任務,Claude Code 做了一個「合理但不是你要的」的解釋,然後快速地在這個錯誤的解釋上建構了大量的輸出。你因為看到大量輸出而感到滿意,沒有在早期確認方向,等到最後才發現整個方向跑偏了。

這時你面對的不只是一個錯誤,而是一大堆建立在錯誤基礎上的輸出,要全部推翻重來。

速度陷阱的特徵:

  • 任務初期沒有讓它彙報理解和計畫,直接讓它開始做
  • 中途沒有設置確認節點,讓它一口氣跑完
  • 看到大量輸出就覺得進展良好,沒有仔細確認方向

系統性的預防機制:

任何超過一個檔案修改的任務,都要求它在開始實作之前先說明計畫:

在開始任何修改之前,先告訴我:
1. 你理解這個任務的目標是什麼
2. 你打算修改哪些檔案、每個檔案改什麼
3. 你預期的完成結果是什麼

等我確認計畫正確之後再開始。

這個步驟花不了幾分鐘,但可以在方向錯誤的時候,把損失控制在「計畫需要修正」而不是「大量輸出需要推翻」。

02|信任陷阱:它說完成就以為完成

這個失敗模式的危險程度在於:它不會立即顯現。程式碼在開發環境跑得好好的,測試通過,它也說完成了。問題在幾天後、幾週後、或是上線後才出現。

陷阱的機制:

Claude Code 的語氣天然傾向於確定。它說「完成了」、「測試全部通過」、「這個實作是正確的」,語氣和它真的確定時完全一樣。這不是它在說謊,而是它的輸出模式就是這樣設計的。

加上它確實完成了大部分的工作,很容易讓你建立「它說完成就是完成了」的習慣。這個習慣在低風險任務上沒有明顯問題,但在高風險任務上會積累成嚴重的品質問題。

信任陷阱的幾種典型變體:

變體一:測試通過但測試寫錯了。

它寫的測試在語法上正確,邏輯上也看起來合理,但測試的斷言沒有真正驗證到你在意的行為。測試通過只代表「它寫的測試通過了」,不代表「你在意的行為是正確的」。

變體二:修好了這條路徑,但沒修另一條。

你報告了一個 bug 的具體觸發方式,它修好了那個特定的觸發路徑,但根本原因沒有解決,只是那個特定的情境不會觸發了。其他情境下同樣的問題依然存在。

變體三:功能正確但效能有問題。

它實作的功能邏輯完全正確,測試也通過,但在真實資料規模下有 N+1 query 問題或記憶體洩漏。這類問題在測試環境的小資料量下完全不會顯現。

變體四:當下正確但未來會壞。

它的實作依賴了某個外部系統的當前行為,但這個行為在文件裡標注為「不保證穩定」。它沒有注意到這個標注,你也沒有。

建立系統性的驗證習慣:

不依賴它的自我評估,建立獨立的驗證清單。依任務的風險等級,選擇對應強度的驗證:

低風險任務:
□ 跑相關測試,確認通過
□ 快速掃一遍 diff,確認沒有明顯問題

中風險任務:
□ 跑完整測試套件
□ 逐行看 diff
□ 讓它做自我 review
□ 確認測試本身的邏輯正確

高風險任務:
□ 以上所有步驟
□ 在 staging 環境實際執行
□ 考慮讓另一個人也 review
□ 確認邊界案例和錯誤路徑都有測試

03|脈絡陷阱:Session 狀態污染

這個失敗模式很隱蔽,因為它的症狀看起來像「Claude Code 變笨了」,而不是「我的工作流程有問題」。

陷阱的機制:

在一個 Session 裡,Claude Code 對 codebase 的理解是動態建構的。Session 初期它讀了某些檔案,建立了某個理解。隨著對話進行,這個理解會被後續的資訊修改、補充、有時也會被覆蓋。

問題是,當 Session 變長、做了多件事之後,它的理解可能處於一個混合狀態:部分基於 Session 初期讀取的舊版程式碼,部分基於它自己修改後的新版程式碼,部分基於你中途補充的資訊。這個混合狀態不是任何一個時間點的真實狀態,是一個被污染的脈絡。

脈絡污染的具體症狀:

  • 它對同一個 function 的描述在 Session 前期和後期不一致
  • 它修改了一個它「以為」還是舊版的東西,但那個東西其實已經在這個 Session 裡被改過了
  • 它引用了一個你在 Session 中途說「不要用這個做法」的模式
  • 它的回應開始出現自相矛盾的地方

脈絡污染的高發情境:

  • Session 持續超過 30-40 個來回
  • 在同一個 Session 裡先做了 A,又根據 A 的結果做了 B,又根據 B 的結果做了 C
  • 中途多次改變了任務的方向或範圍
  • 讓它讀了大量的檔案,context 佔用量很高

預防和處理:

預防的根本方法是控制 Session 的長度和範圍,一個 Session 只做一件清楚定義的事。

當你感覺到脈絡污染的症狀時,不要繼續在同一個 Session 裡試圖修正,直接開新 Session。在新 Session 的開場,明確說明當前的狀態:

新 Session。當前狀態:
- 剛完成了 [已完成的任務],相關 commit 是 [commit hash]
- 接下來要做的是 [新任務]
- 特別注意:[任何需要明確說明的當前狀態]

請先用 git log --oneline -5 確認當前的 commit 狀態,
再開始工作。

04|規模陷阱:小資料通過、真實環境爆炸

這個失敗模式的代價往往在上線後才顯現,而且很難快速回滾。

陷阱的機制:

Claude Code 在開發和測試環境裡工作。這些環境的資料量通常遠小於生產環境,並發量也低,資料的分布也比生產環境乾淨。它寫出來的程式碼在這個環境裡完全正確,但有些問題只有在生產環境的規模下才會顯現。

規模陷阱的幾種常見形式:

N+1 Query 問題:

它實作的查詢邏輯在 10 筆資料下執行時間 50ms,完全感覺不到問題。但在 10 萬筆資料下,同樣的邏輯可能需要執行 10 萬次資料庫查詢,讓整個系統癱瘓。

記憶體問題:

它把整個查詢結果載入記憶體再做處理。測試資料 100 筆,完全沒問題。生產資料 100 萬筆,記憶體直接爆掉。

鎖競爭問題:

它的實作在低並發下完全正常。但在生產環境的高並發下,某個地方的鎖競爭讓系統的吞吐量驟降,甚至造成死鎖。

資料品質問題:

測試資料是乾淨的,每個欄位都有值,格式都正確。生產資料裡有 null 值、有格式異常的歷史資料、有各種邊界情況。它的程式碼沒有處理這些情況,上線後第一個不乾淨的資料就讓系統崩潰。

在指令層面預防規模問題:

對任何涉及資料查詢或處理的任務,在指令裡明確說明規模預期:

實作這個查詢功能時,請考慮以下規模:
- 資料量:orders 表目前有 500 萬筆,每天新增約 1 萬筆
- 並發:峰值約 200 個並發請求
- 資料品質:有約 3% 的歷史資料有 null 欄位

實作完成後,請主動說明:
1. 這個實作在上述規模下的預期效能
2. 有沒有潛在的 N+1 query 問題
3. 記憶體使用量的預期
4. 對 null 值的處理方式

讓它在實作時就考慮規模問題,比事後發現問題再修要省很多時間。

05|依賴陷阱:套件升級的隱性風險

套件版本管理是一個看起來簡單、實際上充滿隱性風險的領域。讓 Claude Code 管理套件升級時,這些風險會被放大。

陷阱的機制:

Claude Code 對套件版本的知識來自訓練資料的截止日期。它可能不知道某個套件在最新版本裡有 breaking change、某個套件的行為在新版裡有細微但重要的改變、或是某個套件目前有已知的安全漏洞需要特定方式處理。

更危險的是,它傾向於把「測試通過」等同於「升級安全」。但如前所述,測試通過只代表現有測試覆蓋的行為沒有改變,不代表所有行為都沒有改變。

依賴陷阱的幾種形式:

直接依賴的 breaking change:

它把某個套件從 v2 升到 v3,v3 有 breaking change,但它沒有仔細讀 CHANGELOG,或是它的訓練資料裡沒有這個版本的資訊,結果某些功能在新版下行為改變了,但測試沒有覆蓋到。

間接依賴的連鎖反應:

你的直接依賴沒有升版,但它升級了一個套件的間接依賴。間接依賴的行為改變比直接依賴更難追蹤,因為你通常不會直接意識到它的存在。

時間炸彈:

新版套件標注了某個 API 為 deprecated,會在下一個 major version 移除。現在還能用,但六個月後當你再次升級,這個 API 消失了,造成意外的 breaking change。

正確的套件升級流程:

不要讓 Claude Code 一次升級所有套件。給它一個嚴格的流程:

套件升級流程(每次只升一個):

1. 確認目標套件的當前版本和目標版本
2. 讀取該套件從當前版本到目標版本的 CHANGELOG,
   特別標注所有 breaking change 和行為改變
3. 評估這些改變對我們的 codebase 的影響
4. 如果有影響,先說明需要哪些對應的程式碼修改,
   等我確認後再執行升級
5. 執行升級
6. 跑完整測試套件
7. 特別針對 CHANGELOG 裡標注的改變,
   確認對應的行為是否如預期

每個套件升級成功後單獨 commit,再進行下一個。

06|理解陷阱:codebase 是它寫的但你看不懂

這是六個失敗模式裡最長期、影響最深遠的一個。它不會在某次任務後立即爆發,而是隨著時間慢慢積累,最終讓整個專案的可維護性崩潰。

陷阱的機制:

Claude Code 的速度和能力讓你很容易養成一個習慣:接受它的輸出,確認能跑,然後繼續下一個任務。這個習慣在短期內提高了效率,但每次這樣做,你對自己 codebase 的理解就下降一點。

累積下來,你的 codebase 裡有越來越多你能跑但說不清楚的程式碼。你知道它能做什麼,但你不知道它為什麼這樣實作、它依賴了什麼假設、它在什麼情況下會出問題。

這個狀態在日常正常運作時不會有明顯症狀。但當問題出現時——上線後的 bug、效能問題、需要修改某個你沒有真正理解的模組——你會發現自己完全無法獨立處理,只能再次依賴 Claude Code。這是一個自我強化的依賴循環。

理解陷阱的早期警訊:

  • 有人問你「這段程式碼在做什麼」,你說不清楚
  • 你需要修改一個模組,但你不確定改了之後會影響哪些地方
  • 你看著一段程式碼,知道它是最近加的,但想不起來為什麼這樣寫
  • 你在 code review 裡批准了一個你沒有真正理解的 PR

在工作流程裡建立理解的機制:

第一,讓它解釋,不只是讓它做:

實作完成後,用非技術語言解釋這個實作:
1. 整體的設計思路是什麼
2. 最關鍵的三個設計決策是什麼、為什麼這樣決定
3. 這個實作依賴了哪些假設,如果假設不成立會怎樣
4. 未來如果要修改這個模組,最需要注意什麼

第二,定期進行「理解審計」:

每隔一段時間,隨機選取幾個最近由 Claude Code 生成或修改的模組,試著向自己解釋它在做什麼、為什麼這樣做。說不清楚的地方,回去讀程式碼,或是讓 Claude Code 解釋。

第三,讓理解成為完成標準的一部分:

這個任務的完成標準包括:
- 功能正確(測試通過)
- 我能夠向團隊成員解釋這個實作的設計思路
  (你需要在完成後給我一個可以用來解釋的摘要)

最根本的心態:

Claude Code 是你的工程夥伴,不是你的替代品。它寫的程式碼是你的 codebase,你需要對它負責。你能夠對某段程式碼負責的前提,是你真正理解它。

速度是手段,理解是底線。不能理解的程式碼,不管跑得多好,都是未來的風險。


▌8. 團隊協作

前七個主題討論的大多是個人使用的視角。但在真實的工程環境裡,Claude Code 通常不是只有你一個人在用。當整個團隊都在使用 Claude Code,而每個人的使用方式、設定、和品質標準都不一樣,問題就會從個人層面升級到組織層面。

以下為你說明:如何讓 Claude Code 在團隊裡成為一個一致的、可預期的工程工具,而不是每個人各自為政的個人助理。

01|讓 Claude Code 的行為在團隊內一致

當團隊裡有五個人都在用 Claude Code,如果每個人的設定不同,會發生什麼:

A 的 Claude Code 知道這個專案不能直接改 migration 檔案,B 的不知道。A 產出的程式碼遵循 repository pattern,C 產出的直接在 service 層操作資料庫。D 的 Claude Code 每次完成任務都會跑測試驗證,E 的從來不跑。

結果是:codebase 裡混雜了不同風格、不同規範、不同品質標準的程式碼。Code review 變得困難,因為 reviewer 不確定哪些差異是刻意的技術選擇,哪些是 Claude Code 自己發揮的結果。技術債以前所未有的速度累積。

一致性的核心機制是共享設定:

把所有影響 Claude Code 行為的設定納入版本控制,讓每個人 clone 專案之後,Claude Code 的行為就自動一致。

專案根目錄/
├── CLAUDE.md                    # 專案層級規範
└── .claude/
    ├── config.json              # 工具設定(MCP server 等)
    └── skills/                  # 共享的 Skill 庫
        ├── add-endpoint.md
        ├── write-migration.md
        ├── code-review.md
        └── debug-issue.md

CLAUDE.md 作為團隊規範的載體:

CLAUDE.md 不只是給 Claude Code 看的文件,也是團隊工程規範的一份具體化記錄。它應該涵蓋的內容和一份好的「新人入職工程規範」高度重疊:

# [專案名稱] 工程規範

## 架構概述
[專案的分層架構說明]

## 核心規則
這些規則適用於所有程式碼修改,
不管是人寫的還是 AI 生成的:

1. Service 層不直接操作資料庫
2. 所有 public method 必須有 type hint
3. 所有新功能必須有對應的測試
4. Migration 只能透過 alembic revision 新增,
   不能直接修改現有 migration 檔案

## 禁止事項
[明確列出絕對不能做的事]

## 驗證標準
每次任務完成的最低驗證標準:
- pytest 相關測試通過
- mypy 無錯誤
- ruff check 無警告

這份文件同時服務兩個讀者:Claude Code(每個 Session 自動載入)和新加入的團隊成員(了解專案規範的第一份文件)。

02|Skill 和 CLAUDE.md 的共同維護機制

共享設定納入版本控制只是第一步。更難的問題是:這些設定怎麼維護?誰負責更新?更新的流程是什麼?

沒有維護機制的常見結果:

CLAUDE.md 在專案初期寫好,之後就沒有人更新。六個月後技術規範已經改變,但 CLAUDE.md 還停留在舊版,Claude Code 照著過時的規範做事,產出不符合現在標準的程式碼。Skill 也一樣:寫好之後沒有人維護,隨著 codebase 結構改變,Skill 的輸出品質越來越差,最後大家就不用了。

建立維護機制的幾個具體做法:

第一,指定維護負責人。

CLAUDE.md 和 Skill 庫應該有明確的負責人(通常是 tech lead 或 senior engineer)。負責人不需要獨自維護所有內容,但需要確保維護工作有在發生,以及評估 PR 裡對這些檔案的修改。

第二,讓更新成為日常工作流程的一部分。

不要等到「有空的時候」再更新 CLAUDE.md,因為那個時間通常不會到來。把更新嵌入現有的工作流程:

Sprint 結尾的 retrospective 增加一個固定議題:
「這個 Sprint 裡,有沒有需要更新 CLAUDE.md 或 Skill 的地方?」

技術決策被做出時(例如換了 ORM、改了錯誤處理模式),
把更新 CLAUDE.md 列為這個決策的交付物之一。

新的 Skill 需求出現時(例如有人說「我每次都要重複說同樣的事」),
把建立 Skill 列為技術工作的一部分。

第三,Skill 和 CLAUDE.md 的 PR 需要特別的 review。

對這些檔案的修改影響範圍比一般程式碼更大,因為它會改變所有人的 Claude Code 行為。建議:

  • 修改 CLAUDE.md 或 Skill 的 PR 需要至少兩個人 review
  • PR 描述要說明為什麼要做這個改動、預期的影響是什麼
  • Merge 後主動通知團隊,讓大家知道規範有更新

第四,版本化重要的設定變更。

當 CLAUDE.md 有重大的規範改變(例如從一個架構模式切換到另一個),在 CHANGELOG 裡記錄這個改變,說明從什麼版本開始適用新規範。這樣當你在看舊的 code 時,能夠理解為什麼那時候的程式碼和現在的規範不一樣。

03|AI 生成的程式碼如何做 Code Review

當團隊開始大量使用 Claude Code,code review 的性質會改變。reviewer 面對的不再是「同事寫的程式碼」,而是「同事用 Claude Code 生成的程式碼」。這兩者需要不同的 review 策略。

AI 生成程式碼的特徵:

  • 語法和風格通常正確:Claude Code 很少犯語法錯誤或明顯的風格問題,這些問題在 review 時往往不是重點
  • 邏輯可能過度自信:它的實作看起來完整,但可能在邊界案例上有問題
  • 可能缺乏業務脈絡:技術上正確,但沒有反映業務規則或歷史決策的細節
  • 測試可能測錯方向:測試覆蓋率看起來不錯,但測試在驗證錯誤的行為

針對 AI 生成程式碼的 review 重點調整:

傳統 code review 的重點通常是:語法、風格、邏輯、測試覆蓋。

針對 AI 生成程式碼,重點應該調整為:

降低關注:
- 語法正確性(通常不是問題)
- 基本風格一致性(如果有 linter 自動檢查)

提高關注:
- 業務邏輯的正確性
  (它有沒有正確理解業務規則,不只是技術規則)
- 邊界案例和錯誤路徑
  (測試有沒有覆蓋到真實環境會遇到的情況)
- 對現有架構的影響
  (它的修改有沒有破壞任何隱性的架構假設)
- 可維護性
  (這段程式碼,不看 Claude Code 的生成脈絡,
   還能不能被人理解和維護)

PR 描述的新標準:

當使用 Claude Code 生成的程式碼提 PR,PR 描述應該包含:

## 這個 PR 做了什麼
[功能描述]

## 技術決策說明
[說明主要的設計選擇和原因,
 特別是那些 Claude Code 自己做的判斷]

## 驗證方式
[說明如何驗證這個 PR 的正確性,
 包括測試覆蓋了哪些情境]

## 已知限制
[任何已知的邊界案例、效能限制、
 或未來需要改進的地方]

## AI 生成的部分
[說明哪些部分是 Claude Code 生成的、
 哪些是人工調整的,
 以及人工 review 的重點在哪裡]

最後一個區塊「AI 生成的部分」值得特別說明。透明地標注哪些是 AI 生成的,讓 reviewer 知道把注意力集中在哪裡,也讓未來維護這段程式碼的人理解它的來源和背景。

04|什麼應該讓 Claude Code 做,什麼應該人自己寫

這是一個需要團隊明確討論和達成共識的問題,不能讓每個人各自決定。

不同的分工策略會產生截然不同的結果:

如果沒有共識,自然的傾向是「什麼都讓 Claude Code 做,因為它快」。這個策略在短期內提高了速度,但長期會讓團隊對 codebase 的理解集體下降,形成前一章提到的理解陷阱的團隊版本。

一個實用的分工框架:

以「重要性」和「重複性」兩個維度來判斷:

高重要性 + 低重複性:
→ 人主導,Claude Code 輔助
範例:核心業務邏輯設計、架構決策、
      安全關鍵的實作、性能關鍵路徑

高重要性 + 高重複性:
→ 人設計 Skill,Claude Code 執行,人驗證
範例:標準的 CRUD 實作、測試撰寫、
      API endpoint 新增

低重要性 + 低重複性:
→ Claude Code 主導,人做基本確認
範例:文件整理、程式碼格式化、
      簡單的重構

低重要性 + 高重複性:
→ 完全自動化,不需要 Claude Code 介入
範例:linting、格式化、簡單的 CI 檢查

有幾類程式碼建議永遠由人主導撰寫:

第一,認證和授權邏輯。這類程式碼的錯誤影響面廣,且錯誤的方式往往不會被一般測試發現。人需要深入理解這段邏輯,不能只是確認「測試通過」。

第二,資料 migration。每個 migration 都是不可逆的,錯誤的 migration 在生產環境造成的損失可能無法完全復原。需要人完全理解並承擔責任。

第三,涉及金融或法律合規的邏輯。這類邏輯的正確性標準不只是技術層面的,還有業務和法律層面的要求,人需要完全理解並能夠解釋給非技術人員。

第四,系統核心的抽象層。例如你的 base model、核心的 middleware、共用的 utility。這些東西影響整個 codebase,對它們的理解深度決定了你對整個系統的掌控程度。

05|團隊對 Claude Code 輸出的共同品質標準

最後一條,也是讓前面所有討論能夠落地的關鍵:團隊需要對「什麼樣的 Claude Code 輸出是可接受的」有明確的共同標準。

沒有共同標準的問題不只是品質不一致,而是當問題發生時,團隊沒有共同的語言討論問題,也沒有共同的基準評估改進。

建立共同品質標準的幾個層面:

第一層:技術標準(客觀可測量的)。

這部分相對容易達成共識,因為有客觀的衡量方式:

## 技術品質標準(所有程式碼,不管來源)

自動化檢查(CI 強制執行):
- pytest 全部通過
- mypy 無錯誤
- ruff check 無警告
- 測試覆蓋率不低於目前的基準線

人工確認:
- 新增的 public method 有 docstring
- 有 breaking change 時有對應的 migration guide
- 效能關鍵路徑有 benchmark 數據

第二層:可理解性標準(主觀但可討論的)。

這部分比較難量化,但可以用具體的問題來評估:

## 可理解性標準

reviewer 應該能夠回答以下問題:
1. 這段程式碼解決的業務問題是什麼?
2. 主要的設計選擇是什麼、為什麼這樣選?
3. 這段程式碼在什麼情況下會出問題?
4. 如果需要修改,最需要注意什麼?

如果 reviewer 回答不出這些問題,
PR 應該要求 author 補充說明,
而不是直接 approve。

第三層:流程標準(確保標準被執行的機制)。

光有標準不夠,需要機制確保標準被執行:

## 流程標準

PR 提交前:
- Author 必須自行跑完所有自動化檢查
- Author 必須完成 PR 描述的所有區塊

PR review 時:
- 至少一個 reviewer 必須實際在本地跑過這段程式碼
- 涉及高風險領域的 PR 需要兩個 reviewer

Merge 後:
- 在 staging 環境驗證功能正常
- 如果這個 PR 改動了 CLAUDE.md 或 Skill,
  通知團隊並在下次同步時說明

讓標準演進,不要讓它僵化:

品質標準不是一次定好就永遠不變的。隨著團隊對 Claude Code 的使用經驗累積、隨著專案的規模和複雜度增加、隨著遇到的問題改變,標準也應該跟著調整。

建議每個 quarter 回顧一次品質標準:

回顧問題:
1. 這個季度因為品質問題造成的最大問題是什麼?
   現有標準有沒有能夠預防它?
2. 現有的哪些標準在實際執行中太困難、
   導致大家繞過它而不是遵守它?
3. Claude Code 的使用方式有沒有改變?
   標準需要對應調整嗎?

標準的目的是讓團隊能夠持續地產出可信賴的程式碼,不是讓標準本身成為目的。如果某條標準在實際執行中產生的阻力大於它提供的保護,它需要被修改,而不是被繞過。

▌結語

八章寫完,回到最根本的問題:Claude Code 改變了什麼,沒有改變什麼。

改變了的:

執行速度。一個需要三天的重構,可能在幾小時內完成。一份需要半天的技術文件,可能在一個小時內有初稿。這個速度的改變是真實的,不是誇大的。

沒有改變的:

判斷什麼值得做,需要你。理解業務脈絡,需要你。承擔技術決策的後果,需要你。維護一個可以被人理解和修改的 codebase,需要你。對你的系統在任何情況下的行為負責,需要你。

Claude Code 把執行的成本降低了,但它沒有降低判斷的成本,也沒有降低理解的必要性。如果你用 Claude Code 的速度產出了大量你不理解的程式碼,你沒有提高效率,你只是把問題往後推,而且因為速度更快,問題累積的速度也更快。

最後一個實際建議:

定期問自己這個問題:「如果 Claude Code 明天停止服務,我的 codebase 的狀況如何?我還能繼續維護它嗎?」

如果答案是肯定的,你用 Claude Code 的方式是對的。如果答案是否定的,值得認真思考工作流程的哪個環節需要調整。

工具服務於人,不是人依賴工具。


▌參考資料

1個讚