Theme / v0.10.8

Codebase Memory

程式碼記憶與知識圖譜 MCP

實戰範例

實戰範例 010:找出誰呼叫了這個函式:入門影響分析

用 trace_path 一次列出某個函式的所有呼叫者,理解重構前的影響範圍,取代手動 grep。

情境

你想改 UserController.create 的回傳格式。問題是:這個函式被誰呼叫?改了之後哪些地方會跟著掛掉?

傳統做法是 grep -r "UserController" src/,然後一筆一筆過濾掉型別 import、註解、字串字面值,剩下真正「執行時會呼叫到」的地方。這個過程在大專案裡很花時間,而且容易漏掉透過動態分派或回呼(callback)進來的路徑。

CM 的 trace_path 工具能替你把這件事做完,而且一次給你完整的呼叫鏈。


前置作業

延續範例 009 的 todo-api 專案,假設圖譜已經建好。如果你還沒做這一步,先回到 009 跑一次 initindex


步驟一:用 grep 看看會找到什麼

先用熟悉的方式起手:

grep -rn "UserController.create" src/

結果可能是:

src/controllers/UserController.ts:12:  async create(req, res) {
src/routes/index.ts:8:router.post('/users', UserController.create);
tests/user.test.ts:24:    const res = await UserController.create(req, fakeRes);

看起來三筆。第一筆是定義本身,第二筆是 route 綁定,第三筆是測試。表面上夠用,但有幾個風險:

  • 如果有人寫 const create = UserController.create; 然後再 create(req),grep 找不到。
  • 如果 route 用了 middleware 包一層,grep 看到的行號不一定是最終呼叫點。
  • 註解或字串裡的 UserController.create 會誤中。

步驟二:改用 trace_path

── 跟 AI 對話 ──

  • You:

    列出所有會呼叫 UserController.create 的地方,我要看完整呼叫鏈。
  • AI:

    我用 trace_pathUserController.create 往上游追,方向是 inbound(被誰呼叫)。

    CM 呼叫: trace_path(function_name: "UserController.create", direction: "inbound") CM 回傳(摘要):

    caller: src/routes/index.ts#POST /users  (邊: HTTPRouteDependency)
      └─ calledBy: UserController.create  @ src/controllers/UserController.ts:12
    
    caller: tests/user.test.ts#24  (邊: test call)
      └─ calledBy: UserController.create
    
    caller: app/jobs/SyncUserWorker.ts#run  (邊: CALLS)
      └─ calledBy: UserController.create  (透過 const { create } = UserController 別名呼叫)

    分析: 除了 grep 看得到的 route 與測試之外,還有一個背景 Worker SyncUserWorker.run 透過解構賦值呼叫 create。這正是 grep 容易漏掉的地方。


步驟三:grep 與 trace_path 對照

比較項目 grep trace_path
找到的呼叫點 3 個(含 1 個定義) 2 個真正呼叫者 + 1 個隱藏別名呼叫
別名/destructuring ❌ 漏掉 const { create } = ... ✅ 追得到
中間經過 middleware ❌ 只看到綁定行 ✅ 給邊的型別 (HTTPRouteDependency)
註解誤中 ✅ 容易 ❌ 不會
看得到邊的型別 ✅ CALLS / test call / HTTPRouteDependency

trace_path 給的不只是「檔名行號」,還有「這條呼叫是什麼性質」。route 綁定、測試呼叫、普通 CALLS,每條邊都帶型別標籤,讓你一眼分辨哪些是 production 路徑、哪些只是測試覆蓋。


步驟四:用結果規劃重構

從上面的清單,你的重構計畫會變成:

  1. UserController.create 本身:回傳新格式。
  2. 更新 route:src/routes/index.ts 不用改,但前端呼叫端要對應更新。
  3. 更新測試:tests/user.test.ts 的斷言要跟著改。
  4. 檢查 Worker:SyncUserWorker.run 是背景任務,確認它對新格式的相容性,這是 grep 容易遺漏、卻可能在 production 才爆開的點。

四個地方一次列完,不用反覆搜尋。


步驟五:呼叫鏈太深時調 depth

預設 trace_path 只展開一層呼叫者。如果你改的是底層函式,想看「呼叫者的呼叫者」這種第二層關係,把 depth 加大:

  • You:

    我想看 UserController.create 的兩層呼叫鏈,找出誰「間接」會走到這個函式。
  • AI:

    我把 depth 設成 2,方向維持 inbound。

    CM 呼叫: trace_path(function_name: "UserController.create", direction: "inbound", depth: 2) CM 回傳(摘要):

    [層 1] src/routes/index.ts#POST /users → UserController.create
    [層 2]   └─ apps/web/src/api/users.ts#createUser → POST /users
    [層 2]   └─ tests/integration/user.flow.test.ts → POST /users
    
    [層 1] app/jobs/SyncUserWorker.ts#run → UserController.create (別名呼叫)
    [層 2]   └─ app/jobs/QueueConsumer.ts#handle → SyncUserWorker.run

    分析: 往上第二層看得到前端 API client users.ts#createUser 與 integration 測試。這些地方雖然不直接 import UserController,但只要 create 的回傳格式變了,前端 client 跟著要動。

depth 愈大,看到的間接呼叫者愈多,但也愈雜訊。實務上從 depth: 1 開始,看不夠再加,不要一次调到 5。

── inbound 與 outbound 的差別 ──

trace_pathdirection 決定追的方向:

方向 意思 用在什麼時機
inbound 誰呼叫我(上游) 改函式前評估影響面
outbound 我呼叫了誰(下游) 看一個函式依賴哪些東西
both 雙向都追 第一次理解某函式在系統裡的位置

重構前評估影響面,用 inbound;想理解某函式到底做了哪些事,用 outbound 把它的下游依賴展開。


關鍵學習點

  • trace_path 是入門版影響分析:一個查詢列完所有呼叫者,方向 inbound 代表「我被誰呼叫」。
  • grep 會漏掉別名呼叫:const { create } = UserController 或動態分派,grep 找不到,trace_path 可以。
  • 每條邊帶型別:route 綁定、測試呼叫、普通 CALLS 分得清楚,規劃重構時不會把測試當 production。
  • 重構前先跑一次:把 inbound 清單當作 checklist,逐一處理,避免改 A 炸 B。下一篇範例 011 會示範如何用 get_architecture 從更高視角看整個專案。