Appearance
總評:值得學什麼、可以改進什麼
這份總評的閱讀方式
每個設計決策我都分成三個部分:
- 做了什麼:簡短描述這個設計
- 為什麼好 / 為什麼有問題:優缺點分析
- 你可以怎麼用:帶走的實戰建議
值得學習的 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.ts、initPlugins.ts、initMcp.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——建立 AntPromptStrategy 和 ExternalPromptStrategy,在入口處選擇,之後的程式碼不需要再判斷。
對你的啟示:如果你的程式碼有「根據使用者類型走不同分支」的模式,考慮用策略模式集中管理。
4. 循環依賴的延遲 require 解法
問題:幾十處 const getX = () => require('./x.js') 的延遲載入,都是為了打破循環依賴。
為什麼有問題:TypeScript 對 require() 的型別推斷不如靜態 import。延遲 require 容易忘記處理 null。這些是架構問題的症狀,不是根本解決方案。
怎麼改:重新設計模組邊界。把共享的型別和介面提取到獨立的 types/ 模組中,打破循環。
對你的啟示:如果你需要用延遲 require 打破循環依賴,先想想能不能重構模組邊界。
5. 缺乏安全審計追蹤
問題:權限決策的記錄主要用於產品分析(logEvent),沒有獨立的安全審計日誌。
為什麼有問題:如果發生安全事件,很難回溯「AI 為什麼被允許執行那個命令」。分析日誌可能被清除或聚合,但安全審計需要完整的、不可刪除的記錄。
怎麼改:建立獨立的安全審計日誌,記錄每個權限決策(允許/拒絕/使用者確認)和原因。
對你的啟示:如果你的 AI 應用能執行有影響的操作,建立獨立的安全審計日誌——和普通的應用日誌分開。
最後的反思
讀完這份分析後,問自己三個問題
我的 AI 應用最需要學習 Claude Code 的哪一個設計? 不要全部學——選出最能解決你目前問題的 1-2 個,優先實施。
Claude Code 的哪些「問題」在我的專案中也存在? 比如提示詞分散、循環依賴、缺乏安全審計。趁早修,比等它長大了再修容易。
如果我要從零開始設計一個 AI Agent,我會怎麼設計它的 prompt 系統和 context 管理? 用這份分析作為參考,但不要照搬——你的使用場景和 Claude Code 不一樣,你需要的設計也不同。