工程 · 2026.10.03 · 3 min · 更新於 2026.10.26
抓 Rust 與 TypeScript 的循環依賴:自己寫模組依賴掃描器,分層規則放進 CI
後端 18 個 package 之間有好幾組循環依賴,只靠人記。我寫了一支不用編譯的掃描器,產出能點進程式碼的架構圖、把分層規則放進 CI,同一天清掉 23 條既有違規,只有 2 條需要注入。
閱讀設定
Rust 後端 18 個 package 之間有好幾組循環依賴,例如 lending 和 subscription 互相引用,repo 裡卻沒有任何依賴規則,全靠人記。
最後的做法是自己寫一支不編譯、直接讀原始碼的掃描器,Rust 後端和 TypeScript 前端共用,規則放進 CI。
分層照現況排之後,只有 23 條違規,同一天全部清掉。真的需要注入的只有 2 條。
起點是我想要一張架構圖:從分層總覽一路點進子模組、再點進程式碼,而且清楚定義哪些模組可以互相依賴。
現成工具接不起來
cargo-modules、dependency-cruiser、ESLint 的 boundaries 規則都看過。
它們各管一邊,規則格式跟圖接不起來,後端的工具還要先編譯。
所以我寫了一支只用 Node 內建模組的工具。Rust 認 crate::、super:: 這類路徑和透過 State 的存取,TypeScript 認 import、export … from 和 import()。
整個檢查約半秒。
圖是單一 HTML,內嵌原始碼約 9 MB,不進版控。它是原始碼的衍生物,commit 進去的話,每個 PR 都會多一份 9 MB 的 diff。
規則:只能往下依賴
單位由上到下排成幾列,只能依賴排在自己下面那些列的單位。同一列互不依賴,往上也不行。
同一個 package 裡另外規定 controller → service → repository → entity,只能往後依賴。
例外分成兩份清單:allow 放設計上就要有的例外,debt 放現有的違規。
兩份清單只要有條目對不到實際的依賴,檢查就失敗。依賴已經拿掉、清單沒跟著刪,CI 會提醒,清單不會留著過期的東西。
後端 CI 多一個檢查步驟,前端掛在 npm run lint 裡,新的反向依賴在 PR 就擋下來。

分層照現況排
照理想的分層排,會一次多出上百條待清,清單長到沒人看。
所以分層照現況排,挑違規最少的順序,先擋住新的違規,再慢慢清。
例如 subscription 排在 lending 下面,因為放貸引擎讀方案上限和訂閱狀態有 10 處,反方向只有績效費的計算在讀放貸資料。
照這個順序,後端 700 個檔、4,744 處引用裡只有 22 條違規,前端 1 條。分層跟現有程式大致吻合,循環集中在少數幾個檔。

分層順序是 agent 照違規數排的,我沒有逐條看過。之後有意見,改規則檔就好。
23 條違規,大多靠搬檔
清法照這個順序試:先搬檔,不行再改分層,最後才注入。
大多數是檔案放錯層,搬過去就好,例如 2FA step-up 驗證搬到 auth、Telegram 綁定端點搬到 notification。
有兩組是分層畫得太粗。績效費和 apikey 的 service、controller 本來就要讀寫放貸的資料,所以排到 lending 上面,entity 和 repository 留在下面。
真的需要注入的只有 2 處:放貸觸發績效費結算、通知中心讀有效公告,兩邊真的互相需要。
做法是依賴注入:下層定義 trait(Rust 的介面),實作寫在上層,啟動時再傳進去。
注入放在最後,是因為注入之後,從下層的程式看不到實際呼叫了誰,讀程式的人要多跳一層。
清完之後 debt 是空的,行為和 API 網址都沒變,2,793 個測試全過,後端同一天部署到 PROD。

盲點:pub use 與巨集
掃描器不懂語意。
透過 pub use 轉出(re-export,把別處的項目從自己這裡公開)的項目,只算到轉出的那個檔。巨集展開後才出現的路徑,也認不到。
這是不編譯換來的代價,看規則和圖的時候要記得這兩個盲點。

工具
Claude Code mods 實作:翻一個月 session log 做出五個 mod
讓 Claude 讀過一個月的 session log,找出自己一再重複的需求,做成 prompt 上方的 band、status line 和指令,其中最有用的是追蹤 Claude 留下哪些東西的 leftovers。
留言
載入留言中…