Skip to content

總評:值得學什麼、可以改進什麼

這份總評的閱讀方式

每個設計決策我都分成三個部分:

  • 做了什麼:簡短描述這個設計
  • 為什麼好 / 為什麼有問題:優缺點分析
  • 你可以怎麼用:帶走的實戰建議

值得學習的 10 個設計

1. 用 Async Generator 做 AI 對話迴圈

做了什麼:整個 AI 對話迴圈寫成一個 async function*(非同步產生器),用 yield 把每個事件(文字片段、工具呼叫、進度更新)傳出去。

為什麼好

  • 對話天生就是「一段一段產出」的——AI 不是一口氣給你答案,而是逐字產出。產生器的 yield 完美對應這個模式。
  • 不同的使用介面(終端 REPL、SDK、桌面應用)可以用不同方式消費同一個產生器——REPL 逐字顯示、SDK 收集完整回應、桌面應用更新 UI。
  • 取消操作很自然——呼叫 generator.return() 就能停止整個迴圈。

你可以怎麼用:如果你的 AI 應用有串流回應,試試用 async generator 取代 callback 或 EventEmitter。程式碼會更直覺。

2. 把 System Prompt 當成有快取邊界的軟體架構

做了什麼:系統提示詞分成「靜態區」和「動態區」,中間用邊界標記分開。靜態區可以跨使用者共享快取。

為什麼好:在大規模的 AI 應用中,提示快取可以省下 90% 的輸入 token 費用。但快取需要前綴穩定。Claude Code 的設計確保了前綴的穩定性。

你可以怎麼用:把你的 system prompt 分成「不會變的部分」和「會變的部分」。不會變的放前面,會變的放後面。如果你用 Anthropic API,利用 cache_control 參數。

3. 用命名傳達隱藏成本

做了什麼DANGEROUS_uncachedSystemPromptSection() 函數名字故意嚇人,而且強制開發者寫一個「為什麼需要破壞快取」的理由。

為什麼好:文件會過時,但函數名會一直存在。每次看到 DANGEROUS_ 前綴,開發者都會停下來想一想。

你可以怎麼用:在你的程式碼中找出「看起來無害但實際上有成本」的操作。用命名暴露成本:SLOW_fullScan()EXPENSIVE_apiCall()DANGEROUS_deleteAll()

4. 五層壓縮管線的成本排序

做了什麼:從免費(刪除舊訊息)到昂貴(完整 API 壓縮),依序嘗試五種壓縮方式。

為什麼好:大多數情況下,免費方法就能釋放足夠的空間,不需要花錢做完整壓縮。

你可以怎麼用:任何資源管理問題都可以用這個模式。比如記憶體管理:先清快取 → 再清過期資料 → 最後才做 GC。

5. 讓 AI 主動「做筆記」

做了什麼:在 system prompt 中告訴 AI「工具結果可能會被清除,請把重要資訊寫到你的回覆中」。

為什麼好:這是一個巧妙的「記憶外化」策略。AI 會把 5000 行的檔案內容濃縮成 10 行的重點摘要,寫在自己的回覆中。之後即使原始檔案被清除,重點仍然在。

你可以怎麼用:如果你的 AI 應用有「會過期的大量上下文」(比如即時搜尋結果、臨時文件),告訴 AI「把重點記下來」。

6. 工具描述的「失敗預防」模式

做了什麼:不只告訴 AI 怎麼使用工具,還告訴它「什麼情況會失敗」和「失敗了怎麼自救」。

為什麼好:AI 遇到工具錯誤時,不是報錯或卡住,而是自動嘗試描述中提到的替代方案。

你可以怎麼用:在你的每個工具描述中加入「常見失敗原因和解決方法」。比如:「如果搜尋沒有結果,試試放寬搜尋條件或用不同的關鍵字。」

7. 從真實事故數據決定閾值

做了什麼:斷路器的「3 次」上限、壓縮的「20K token」預留——每個數字都有 BigQuery 分析數據支撐。

為什麼好:不是「感覺 5 次差不多」,而是「數據顯示 p99.99 是 17,387,我們上取整到 20,000」。

你可以怎麼用:記錄你的 AI 應用的失敗模式。用數據來設定重試上限、超時時間、快取大小——而不是拍腦袋。

8. 建置時死碼消除

做了什麼:用 feature() 函數在建置時移除不需要的程式碼路徑,而不是在執行時用 if-else 跳過。

為什麼好:外部版本的程式碼體積小 30%、沒有多餘的模組載入。

你可以怎麼用:如果你用 Vite、Webpack 或 esbuild,利用 define 選項做類似的事。

9. 不可變的安全狀態

做了什麼:權限上下文用 DeepImmutable<> 包裝,編譯器阻止任何修改。

為什麼好:把「安全 bug」從「執行時發現」提前到「編譯時發現」。

你可以怎麼用:你的應用中有「絕對不能被意外修改」的狀態嗎?用 TypeScript 的 Readonly<>DeepImmutable<> 來保護它。

10. 同時使用正面和負面指令

做了什麼:「用 Read 工具」+「不要用 cat 命令」。

為什麼好:覆蓋 AI 的訓練慣性。光說「用 A」不夠,還得說「不要用 B」。

你可以怎麼用:如果 AI 總是做某個你不想要的行為,明確禁止那個行為,而不只是鼓勵正確行為。

可以改進的 5 個地方

1. main.tsx 的「上帝物件」問題

問題:4,683 行的 main.tsx 負責太多事——CLI 解析、認證、插件載入、MCP 連線、會話恢復、REPL 啟動。

為什麼有問題:任何修改 main.tsx 的 PR 都容易和其他 PR 衝突。新開發者很難理解「這 4,683 行在做什麼」。

怎麼改:拆成獨立的模組——initAuth.tsinitPlugins.tsinitMcp.ts——每個負責一件事,由 main.tsx 協調呼叫。

對你的啟示:如果你的入口檔案超過 500 行,考慮拆分。

2. 提示詞分散在太多地方

問題:系統提示詞在 prompts.ts、工具描述在各工具的 prompt.ts、上下文在 context.ts、壓縮指令在 compact/prompt.ts。沒有一個地方能看到「AI 看到的完整內容」。

為什麼有問題:當你想修改 AI 的行為時,你不確定該改哪個檔案。「不要用 cat」這個指令同時出現在系統提示詞和 GrepTool 描述中——改了一個忘了另一個就不一致了。

怎麼改:建立一個 prompt registry,集中管理所有提示詞片段,支援搜尋和版本追蹤。

對你的啟示:當你的 AI 應用有超過 5 個不同的提示詞片段時,考慮集中管理。

3. Ant vs External 的條件分支散落各處

問題process.env.USER_TYPE === 'ant' 這個判斷出現了幾十次,分散在整個程式碼庫中。

為什麼有問題:想知道「外部使用者看到什麼」需要搜索所有這些條件分支。容易遺漏。

怎麼改:用 Strategy Pattern——建立 AntPromptStrategyExternalPromptStrategy,在入口處選擇,之後的程式碼不需要再判斷。

對你的啟示:如果你的程式碼有「根據使用者類型走不同分支」的模式,考慮用策略模式集中管理。

4. 循環依賴的延遲 require 解法

問題:幾十處 const getX = () => require('./x.js') 的延遲載入,都是為了打破循環依賴。

為什麼有問題:TypeScript 對 require() 的型別推斷不如靜態 import。延遲 require 容易忘記處理 null。這些是架構問題的症狀,不是根本解決方案。

怎麼改:重新設計模組邊界。把共享的型別和介面提取到獨立的 types/ 模組中,打破循環。

對你的啟示:如果你需要用延遲 require 打破循環依賴,先想想能不能重構模組邊界。

5. 缺乏安全審計追蹤

問題:權限決策的記錄主要用於產品分析(logEvent),沒有獨立的安全審計日誌。

為什麼有問題:如果發生安全事件,很難回溯「AI 為什麼被允許執行那個命令」。分析日誌可能被清除或聚合,但安全審計需要完整的、不可刪除的記錄。

怎麼改:建立獨立的安全審計日誌,記錄每個權限決策(允許/拒絕/使用者確認)和原因。

對你的啟示:如果你的 AI 應用能執行有影響的操作,建立獨立的安全審計日誌——和普通的應用日誌分開。

最後的反思

讀完這份分析後,問自己三個問題

  1. 我的 AI 應用最需要學習 Claude Code 的哪一個設計? 不要全部學——選出最能解決你目前問題的 1-2 個,優先實施。

  2. Claude Code 的哪些「問題」在我的專案中也存在? 比如提示詞分散、循環依賴、缺乏安全審計。趁早修,比等它長大了再修容易。

  3. 如果我要從零開始設計一個 AI Agent,我會怎麼設計它的 prompt 系統和 context 管理? 用這份分析作為參考,但不要照搬——你的使用場景和 Claude Code 不一樣,你需要的設計也不同。

Claude Code 架構設計深度分析