這個範例要解決的問題
你的 TypeScript 前端專案有一個共享工具函式 formatCurrency,定義在 src/utils/format.ts:
export function formatCurrency(amount: number): string {
return `$${amount.toFixed(2)}`;
}
產品需求來了:「要支援多幣別,formatCurrency 必須加上 currency 參數。」 你預計把簽章改成 formatCurrency(amount, currency)。問題是,這個函式在整個專案被呼叫了 30 幾次,散在元件、Hook、測試、後端 API 回應格式化裡。漏改一個,上線就會炸。
你要在動手前,一次性盤點所有呼叫端。
傳統 grep 為什麼不夠
直覺做法是:
grep -rn "formatCurrency" src/
問題馬上浮現:
- 註解與字串也會被命中:
// TODO: formatCurrency should support currency會被算進去,稀釋信號。 - 動態呼叫抓不到:
const fmt = formatters[currency]; fmt(amount);這種間接呼叫,grep 看不出fmt其實指向formatCurrency。 - 改名後的 import 別名:
import { formatCurrency as fmtMoney } from '@/utils/format';在 grep 眼裡是fmtMoney,不是formatCurrency。 - 每次命中還要再
read_file:30 個命中等於 30 次 read_file,光是把上下文湊齊就燒掉數十次工具呼叫。
用 codegraph explore 一次看清楚
先確定專案已經跑過 codegraph init(.codegraph/ 存在)。然後:
codegraph explore "formatCurrency src/utils/format.ts"
Codegraph 回傳的結構化區塊會長這樣:
== Definition (src/utils/format.ts) ==
3 export function formatCurrency(amount: number): string {
4 return `$${amount.toFixed(2)}`;
5 }
== Inbound Callers (32 個呼叫端) ==
直接呼叫:
src/components/Price.tsx:14 formatCurrency(product.price)
src/components/Cart.tsx:42 formatCurrency(item.subtotal)
src/hooks/useCart.ts:21 return formatCurrency(total)
src/api/orders.ts:88 formatCurrency(order.total)
src/utils/format.test.ts:6,10,14 測試案例
...
透過別名呼叫 (import alias):
src/components/Checkout.tsx:7 import { formatCurrency as fmtMoney }
src/components/Checkout.tsx:23 fmtMoney(cart.grandTotal)
透過動態分派呼叫:
src/utils/formatters.ts:30 const fn = registry[name]; // name = 'formatCurrency'
== Outbound Dependencies ==
- Number.prototype.toFixed (built-in)
一份輸出涵蓋了 grep 抓不到的三種邊界情境,還附帶每個呼叫端的檔案與行號。
把輸出轉成重構檢核表
收到上面的 Inbound Callers 清單,把它變成一張工作表:
| # | 檔案 | 行 | 呼叫形式 | 改動策略 |
|---|---|---|---|---|
| 1 | src/components/Price.tsx |
14 | 直接呼叫 | 加 currency prop 並往下游傳 |
| 2 | src/components/Cart.tsx |
42 | 直接呼叫 | 改用 useCart() 已取得的幣別 |
| 3 | src/hooks/useCart.ts |
21 | 直接呼叫 | Hook 簽章擴充 currency 參數 |
| 4 | src/api/orders.ts |
88 | 直接呼叫 | 從訂單物件取 currency |
| 5 | src/components/Checkout.tsx |
23 | 別名 fmtMoney |
同樣改名傳幣別 |
| 6 | src/utils/formatters.ts |
30 | 動態分派 | registry 也要登錄新簽章 |
| … | … | … | … | … |
| 30+ | 測試檔 | 6, 10, 14 | 測試案例 | 補 currency 與多幣別 case |
重點不是這張表的欄位,而是你確定看過了每一個呼叫端。grep 流程要做到這件事得反覆交叉比對,Codegraph 一次給完。
別漏掉「動態分派」這一類
特別注意上面清單第 6 項。src/utils/formatters.ts 透過 registry[name] 把字串對應到函式再呼叫。這是 grep 的死亡地帶:呼叫端檔案裡根本沒有 formatCurrency 這個字串,只有 registry['formatCurrency'] 的字串鍵。
Codegraph 因為解析了 registry 的型別與賦值鏈,能在 Inbound Callers 的「動態分派」區塊中明確標出這條呼叫路徑。少了這條線索,你的多幣別重構會等到 production 才被 QA 發現「特定幣別還是顯示美元」。
重構完成後的驗證迴圈
簽章改完、所有呼叫端都補上 currency 參數後,再跑一次 explore 確認沒有殘留:
codegraph explore "formatCurrency src/utils/format.ts"
新版清單應該符合兩個條件:
- 每個 Inbound Caller 都傳入了
currency參數(檢查呼叫端形式) - 沒有「透過動態分派呼叫」的黑數殘留(該區塊為空或都已更新)
如果還有漏網之魚,Codegraph 會原原本本列出來。這就是以 Codegraph 做閉環驗證的價值。
整理:影響範圍分析的標準動作
把這個範例歸納成可重複套用的三步驟:
1. 探索前
確認 .codegraph/ 存在。新接手的專案先跑一次 codegraph init,後續 watcher 會自動同步。不確定圖譜新舊時,也可以先跑 codegraph status(v1.6.0 新增)查看索引狀態。
2. 探索中
對於要改動的符號,固定用 codegraph explore "symbol path/file.ts",注意 Inbound Callers 是否出現「透過別名呼叫」或「透過動態分派呼叫」這兩個分類。
3. 探索後
把 Inbound Callers 清單轉成工作表逐項處理。改完再跑一次 explore 做閉環驗證。
關鍵學習點
- 改共享函式簽章前,用單次
codegraph explore一次列出所有呼叫端,包括 import 別名與動態分派這兩種 grep 抓不到的邊界。 - Inbound Callers 的分類(直接、別名、動態分派)可以直接轉成重構工作表,不會漏項。
- 重構完再跑一次 explore 即可做閉環驗證,確認所有呼叫端都已跟上新簽章。
- 對 AI 代理而言,這套流程把「影響範圍分析」從數十次工具呼叫壓成一次,正是 Codegraph 標榜的 58% 工具呼叫縮減的典型場景。