格式化 2500 萬行 Ruby:Stripe 的 `rubyfmt` 故事

Stripe 最近分享了他們使用 rubyfmt 在一夜之間格式化整個 2500 萬行 Ruby 程式碼庫的經驗。這項雄心勃勃的計畫凸顯了在極大規模下維護程式碼品質與開發者生產力時所涉及的複雜性與考量。這項任務展示了對一致程式碼風格的重大承諾,利用自動化工具來簡化開發流程並減輕工程師的認知負擔。

選擇在單次原子操作中重新格式化如此龐大的程式碼庫,而非逐步進行,說明了一種經過計算的風險報酬評估。這強調了一種信念:對於大型工程組織而言,統一的程式碼風格至關重要,即使這涉及如此巨大的 diff,導致 GitHub 都難以渲染。

挑戰的規模:2500 萬行

Stripe Ruby 程式碼庫的龐大規模——2500 萬行——是一個相當熱門的討論點。許多人認為這個數字令人震驚,進而對此類系統的性質與架構提出了疑問。

"我對這 25M 行的部分感到震驚!對於單個程式碼庫來說,這是一個完全無法想像的程式碼量。我真的很想了解更多關於這方面的資訊。" — @varun_ch

一家主要的金融處理公司使用 Ruby 也引起了關注,有些人表示擔憂:

"一家主要的金融處理公司用 Ruby 撰寫處理金錢的系統。真可怕。" — @andrewstuart

然而,這種規模也為工具開發提供了獨特的機會。正如一位評論者所指出的,與我們處理的數據相比,程式碼本身通常非常小,這使得大規模操作變得可行。

"關於程式碼的一個洞察是,與我們處理數據的規模相比,程式碼是微小的。即使在等待 token 回傳時,即時的 git 操作和「對所有程式碼執行此工具」都是常態。這個洞察看起來可能很明顯——但如果你在工作時能意識到這一點,你就能為自己和團隊發明一些非常驚人的工具。" — @cadamsdotcom

rubyfmt 的方法:一次到位 vs. 逐步進行

Stripe 選擇在週末進行「一次到位」的重新格式化,這是一種為了避免合併衝突(merge conflicts)的刻意策略。團隊在測試套件的支撐下才敢於執行,但也承認如此大規模的變動具有挑戰性。

這種方法與某些開發者在處理大型程式碼庫時偏好的逐步策略形成對比。逐步方法可能涉及僅在新的 PR 觸及檔案時才進行重新格式化,或者分小批次進行。

"我很驚訝他們選擇了一次性重新格式化。即使是在週末進行,這也肯定會干擾到他們這種規模下許多開啟中的 PR。我過去曾在幾個大型程式碼庫中(幾十萬到幾百萬行)引入格式化工具,我總是透過腳本逐步進行,重新格式化所有未在任何開啟中的 PR 中被觸及的檔案。初始執行重新格式化了 95% 的檔案。" — @hobofan

這種「大爆炸式」方法的優點是整個程式碼庫能立即達成一致性,消除了部分檔案已格式化而其他檔案尚未格式化的過渡期。rubyfmt 工具本身是用 Rust 編寫的,這是對效能要求極高的開發者工具的常見選擇。

確保正確性:完整性檢查

任何自動化格式化工具的一個關鍵面向是確保它僅更改空白,絕不改變程式碼的語義。Stripe 團隊對 rubyfmt 的信心來自於強大的測試。例如,Dart 格式化工具採用了嚴格的內部完整性檢查:

"dart formatter 有一個內部的完整性檢查。它會並行遍歷未格式化和已格式化的字串,並跳過任何空白。如果任何非空白字元不匹配,它會立即中止。這確保了格式化工具唯一改變的就是空白,使得在龐大的程式碼庫上盲目執行它時,風險大大降低。" — @munificent

這種層級的保障是無價的,特別是在處理複雜的語言特性或新語法時,因為它可以防止細微的錯誤因格式化錯誤而潛入程式碼庫中。

程式碼格式化的哲學

投入在格式化上的努力引發了關於其最終價值的辯論,特別是在 AI 越來越多地參與程式碼生成的時代。

"格式化程式碼還有什麼意義嗎?" — @throwatdem12311

"肯定地說,它不再需要具備人類可讀性,隨著 AI 開始為我們撰寫內容,寫入式程式碼(write-only code)的時代終於到來了。為什麼要費心格式化 25m 行的廢料,而且為什麼 AI 要浪費 token 來讓程式碼看起來像人類寫的一樣呢?" — @CrzyLngPwd

然而,程式碼的可讀性對於協作與維護來說仍然至關重要。一個幽默的軼事凸顯了個人偏好與團隊標準之間的緊張關係:

"首席開發者不想費心格式化程式碼,所以我寫了一個名為 makenice 的工具,將他那糟糕的義大利麵式亂碼格式化成具有良好縮排和佈局的東西,以便我們這些正常人閱讀。他非常憤怒……所以我寫了 makenasty,將程式碼格式化成他看起來喜歡的樣子。我只與團隊中的幾個人分享了 makenasty/nice,他們非常喜歡,因為它可以在可讀內容與團隊主管喜歡的內容之間輕鬆轉換。" — @CrzyLngPwd

這說明了雖然格式化看起來像是一個小細節,但它會顯著影響開發者體驗與團隊凝聚力。未來甚至可能會看到從基於文本的格式化完全轉向將程式碼作為解析樹(parse trees)來儲存與操作:

"這真的讓我想起,原則上並沒有任何東西阻止我們儲存解析樹,並透過類似 git 的方式將其公開,這樣我們甚至不需要格式化,更不用說還要解決一整類基於格式化的合併衝突。我的意思是,格式只是你數據上的一種主題——我的意思是程式碼。" — @eigenblake

策略性工具與生產力

rubyfmt 的故事證明了投資於開發者生產力工具的價值。透過優先處理最複雜的語法,rubyfmt 團隊採用了一種建立強大格式化工具的穩健策略。

"考慮到複雜性,假設很簡單:先處理最難的語法,其餘的自然會隨之而來。這總是令人欣慰。我見過有些人陷入了為常見情況設計的陷阱,卻沒意識到大部分的程式碼將是用來處理較不常見的情況。" — @nitwit005

這種方法確保了工具能夠處理它將遇到的所有程式碼範圍,提供可靠且一致的輸出。自動在數百萬行程式碼中強制執行一致風格的能力,將開發者從手動格式化的煩惱中解放出來,讓他們能專注於邏輯與功能。

Stripe 使用 rubyfmt 在一夜之間成功重新格式化其龐大的 Ruby 程式碼庫,這在開發者工具與大規模程式碼管理方面是一項重大成就。它強調了一致程式碼風格的重要性、強大自動化工具的力量,以及在廣闊的工程環境中維持高生產力與程式碼品質所需的策略決策。

Sources