解決方案
在 Ramp,一個機器學習工作流程的單一儲存庫(monorepo)原本是便利的共用空間,後來卻演變成團隊口中的「有點像沼澤」:一份 requirements.txt 的遞移相依套件未鎖定版本,在不同機器之間漂移;格式不一致;沒有自動化測試;型別註解不是缺漏就是有微妙錯誤。工程師愈來愈不敢改動程式碼,深怕弄壞東西,而原本的目標——開發速度——已慢如蝸步。
團隊沒有重寫整個儲存庫,而是在現地採用四項工具:poetry 提供鎖定且可重現的相依性;ruff 負責格式化與靜態檢查(linting);pytest 用來測試高槓桿的共用工具,而非追求完整覆蓋率;mypy 則做漸進式型別檢查。讓這套做法能在活躍的程式碼庫上推行的關鍵,並不是先修好一切。他們先在整個儲存庫上以單一提交執行一次自動格式化工具,然後把該提交的雜湊值加入 .git-blame-ignore-revs,使其不會汙染 blame 歷史。
至於靜態檢查與型別檢查,他們沒有在啟用 CI 之前先修復數百個既存錯誤,而是針對每一項既存違規,以機器產生「# noqa」與「# type: ignore」註解,然後對任何新程式碼啟用嚴格強制執行。ruff 的「RUF100」規則與 mypy 的「warn_unused_ignores」會標記任何在底層程式碼實際修好後已失效的抑制註解,因此忽略清單只會縮小,絕不會無聲地腐化。
結果是一個從未靠命令就「乾淨」的程式碼庫,但從第一天起就對往後的程式碼強制執行,而隨著開發者自然接觸舊檔案,技術債也逐步縮減。
達成的成果
將舊有違規列入豁免,只對新程式碼強制執行
為何有效
全面重寫或一次性大規模清理會在任何強制執行開始前耗費數週,因此團隊選擇對往後程式碼強制執行,而非追溯既往。
以機器產生初始的抑制註解,把無邊無際的人工清理變成一次性的可自動化步驟。
標記未使用的抑制註解意味著技術債只會減少,因為修好一項舊違規就會浮現一個如今已無意義的忽略註解,而 CI 會抓到它。
把重新格式化壓縮成單一提交並將其排除於 git blame 之外,保留了看出某一行最後一次被誰基於真正原因改動的能力。
可套用之處
你不必先有乾淨的程式碼庫才能對它強制執行標準。把所有既存違規一次性消音,只對新程式碼嚴格強制執行,而隨著人們本來就會接觸舊檔案,待辦清單自然會縮減。
後續發展
團隊回報,程式碼庫已達到往後品質得以維持的狀態:poetry 的鎖定檔在開發者機器與 CI 之間提供一致的環境;ruff 在合併前抓到重複字典鍵之類的真實錯誤;mypy 則在單元測試未涵蓋的呼叫點之間抓到簽章變更造成的回歸。該文章把目標定調為恢復進行大規模改動的信心,而非工具本身,並指出團隊仍在研究如何把更多這類負擔從貢獻者轉移到工具上。
追溯依據
參考來源
- Increasing velocity by modernizing a Python codebase Ramp Engineering · builders.ramp.com ·