以慣用方式使用依賴項
每個依賴套件都是為了解決特定形狀的問題而建立的。它的 API 編碼了其模型:它擁有什麼、它留給你什麼、它保證什麼,以及它預期在哪裡被擴展。請撰寫慣用的解決方案,也就是該函式庫自己的文件與規範範例會展示的那種;當問題有不同的形狀(阻抗不匹配)時,請為該工作選擇正確的工具。要避免的錯誤是重新發明輪子、與框架對抗,以及悄悄未充分利用一個為此情況設計的功能。
摩擦是一種警訊:在顯而易見的路徑上出現型別轉換、修補程式或變通方法,表示模型被誤讀或工具是錯誤的。代價最高的違規行為毫無摩擦:它們能通過型別檢查且乍看之下是區域性的,但卻破壞了該函式庫存在的保證,因此要有目的地尋找它們。
撰寫使用依賴項的程式碼
預設路徑。審查、稽核、新增和升級都由此分支出來(下方)。
-
重新陳述問題。 注意 XY 問題:任務經常以解決方案的形式出現,無論來自你或使用者。請將其重新陳述為一般問題:
- "重試上傳三次"是一個重試策略。
- "我需要每個帳戶最新發票的
row_number() over (partition by account_id)" 是每組前 N 筆,ORM 可能可以不用視窗函數來建模。
"這個函式庫做不到 X" 是一個需要在步驟 3 中驗證的主張,無論是誰提出的。
完成條件是:你可以用該函式庫文件會使用的詞彙來命名這個問題。
-
尋找已解決此問題的地方,由近到遠:
- 你所在的框架:工作流程引擎的重試策略與計時器、GraphQL 用戶端的分頁與儲存更新器、測試執行器的假計時器。
- 專案宣告的依賴項(
package.json、pyproject.toml、go.mod、Cargo.toml)以及程式碼庫如何匯入它們。現有用法顯示了團隊慣例,這可能與函式庫的設計不同。 - 專案的共享套件,通常會以專案想要的方式包裝一個依賴項。
- 標準函式庫,針對正在使用的執行時期版本(
engines、.nvmrc、requires-python)。廣泛的函式庫涵蓋的範圍比人們記得的更多:Python 的itertools、collections、functools、pathlib;現代 Node 的fetch、AbortSignal.timeout、util.parseArgs、node:test。
完成條件是:每個來源都已檢查,並且你有一個可能為空的候選清單。
-
從已安裝版本的說明文件學習模型。 從鎖定檔案取得版本;一個來源只有在與之匹配時才算數。依次閱讀:
- 此依賴項的專案指導方針,依照 GUIDANCE.md 找到並維護。它是關於團隊決策的權威來源,也是函式庫模型的快取:當其記錄的版本與鎖定檔案不同時,或當一個主張決定了你的方法時,請在以下來源中確認。
- 參考檢出:依賴項的儲存庫被複製到專案中,或由
opensrc等工具提取,通常在AGENTS.md或CLAUDE.md中有指向。依據鎖定檔案檢查其標籤。其文件、範例和測試是預期用途的最佳記錄。 - 已安裝的套件:匯出的型別及其文件註解,通常會移除文件和範例。
- 線上資源,當上述兩者都沒有文件時:該主要版本的官方文件、概念和指南頁面以及 API 參考;然後是匹配標籤的上游儲存庫。
僅基於公開 API 建構:文件描述的內容,透過宣告的進入點(exports 地圖、
__all__、沒有底線開頭的名稱)存取。閱讀原始碼以了解行為;任何你只在原始碼中找到的內容都是私有的。Semver 僅保護公開 API,不保護其他東西。你對函式庫的記憶只是一個需要與文件對照的假設,而你碰巧開啟的執行時期檔案無法取代它們。完成條件是:在撰寫或判斷使用它的程式碼之前,你可以從文件中回答:
- 它是為了解決什麼而建構的,它刻意遺漏了什麼?
- 它擁有什麼(儲存、快取、生命週期、重試、交易),它產生什麼或要求你宣告什麼(程式碼生成輸出、綱要、型別),每一塊是為你程式碼的哪個部分準備的?
- 它維護什麼保證(資料遮罩、工作流程確定性、交易隔離),在系統的哪些部分之間,以及什麼會悄悄地破壞它?它通常位於你自己的模組之間。
- 哪些公開 API 和記錄的擴充點(選項、外掛、中介軟體、鉤子、逃脫口)涵蓋了這一點,規範範例看起來像什麼,包括哪個模組匯入了什麼?
- 它附帶了什麼檢查(lint 外掛、編譯器或程式碼生成驗證、嚴格的型別模式),以及專案是否執行它們?
-
選擇合適的方案:
- 問題與模型匹配: 使用它。
- 問題部分落在主要 API 之外: 使用該部分的記錄逃脫口(ORM 中的原始 SQL、無頭元件、自訂解析器)。逃脫口是設計的一部分。
- 已安裝版本缺少該功能: 如果變更日誌顯示較新版本有此功能,請依照 UPGRADING.md 提出升級。
- 阻抗不匹配: 使用模型適合的依賴項,或是使用清楚避開此函式庫的純程式碼,並將其置於我們擁有的接縫之後。被安裝或用在附近,並不會讓一個函式庫成為正確的工具。
完成條件是:你已經選擇了一個方案,並且可以根據其模型來證明其合理性。
-
以函式庫的方式,在其公開 API 上撰寫程式碼。 第一個能運作的版本通常不是慣用的。當相鄰的程式碼與規範範例不一致時,遵循規範範例並告訴使用者這個偏差。一邊開發一邊執行函式庫自己的檢查;如果專案關閉了它們,請說明開啟它們會捕捉到什麼。
完成條件是:函式庫的檢查通過,以下沒有任何異味適用(或者每個適用的異味都是你將報告的選擇),並且該函式庫的維護者會認可這些程式碼是該函式庫被按設計使用的方式。
-
當摩擦持續存在時,首先調整我們的設計以符合函式庫的模型;其限制通常編碼了一個教訓(工作流程引擎的決定性規則、GraphQL 用戶端的資料遮罩)。然後重新審視步驟 4。只有在使用者同意下才能違反函式庫,並在提案中和變通方法處的註解中命名你所依賴的未記錄行為。模型與產品需求之間的衝突由使用者決定。
異味:對抗函式庫
每一個都會讓你回到步驟 4。按函式庫存在的目的來排序:被破壞的保證優先於其他一切,即使今天沒有任何東西出錯,而編碼了模型的團隊規則則承襲了模型的嚴重性。
- 被破壞的保證:生成的程式碼用於非為其生成的模組之外(另一個元件的片段型別)、讀取另一個模組的遮罩資料、一個片段或綱要在不相關的呼叫者之間共享、在決定性工作流程內的非決定性。這些能乾淨地通過型別檢查,因此要有目的地搜尋它們。
- 在我們程式碼與函式庫型別交界處的型別轉換或型別檢查器抑制(
as、any、@ts-expect-error、# type: ignore)。 - 私有 API:內部路徑(
pkg/dist/…、pkg._internal)、底線名稱、未記錄的匯出、轉換到函式庫物件的私有欄位、僅在原始碼中找到的行為。不穩定或實驗性的 API 有記錄但不受 semver 保護:僅在文件建議於此處使用時使用它,並說明原因。 - 改變函式庫行為的修補程式(套件管理員修補、猴子補丁、分支複本)。讓函式庫做其文件已承諾之事的修補程式是一個錯誤修正,是正確的做法:將其限制在修正範圍內,並連結上游問題。
- 重複的狀態:為函式庫擁有的東西設立的第二個真相來源,例如鏡像到儲存狀態的 GraphQL 快取,或框架已追蹤的重試計數。
- 在函式庫提供回呼、事件或鉤子的地方,使用計時器、旗標或效果鏈來執行「之後」的程式碼。
- 在每個呼叫點覆寫預設值,或在每次呼叫時都撤銷某個行為的包裝器。
- 一個淺包裝器,主要重新命名它包裝的 API。應用刪除測試:如果刪除它只會將其呼叫移回函式庫,那它就不值得保留。
- 因為整個功能看起來比需要的多,而重新實作其部分功能。
異味:未充分利用函式庫
程式碼在公開 API 上運作良好,但一個為此情況建構的功能會讓它更簡潔或更具宣告性。要找到它,請閱讀你傳遞的每個選項物件的型別,以及步驟 1 中該問題的函式庫指南。
- 多個呼叫組成一個操作:在有 upsert 的地方進行 find-then-create、在有批次 API 的地方進行迴圈寫入。
- 用於重塑輸出的黏合程式碼,而查詢或鉤子接受 select 或 projection 選項。
- 手動重建函式庫追蹤的狀態:載入中、等待中、髒資料或重試旗標。
- 每個呼叫點都有相同的選項,而存在預設值、共用設定、主題或工廠。
- 對於函式庫允許你宣告的事情使用命令式程式碼:綱要、變體、關係、策略。
比例是雙向的:當幾行清晰的純程式碼就能完成工作時,純程式碼就是慣用的。
審查、稽核、新增與升級
- 審查他人的變更:閱讀 REVIEWING.md。
- 稽核程式碼庫對某個依賴項的使用:閱讀 AUDITING.md。
- 新增套件,或升級依賴項或執行時期:先閱讀 UPGRADING.md。
- 尋找、撰寫或更新某個依賴項的專案指導方針:閱讀 GUIDANCE.md。
回覆: 承載你未撰寫行為的函式庫功能;針對你自己撰寫的一般用途程式碼,你檢查了什麼以及為什麼沒有合適的;你跳過的每個依賴項,以及其模型中不匹配的部分;以及你保留的每個異味及其原因。當你學到了關於該依賴項的持久知識時,請依照 GUIDANCE.md 添加你建議的指導方針。