世界を動かす技術を、日本語で。

25M行のコードベースを一晩でフォーマットする

概要

この記事は、 Striperubyfmt プロジェクトにより、 2,500万行 のコードベースを一晩で自動整形した事例を紹介。 開発者生産性向上のための 技術的課題解決策 を解説。 rubyfmtの導入背景や、 大規模リファクタリング の手法を説明。 Stripeの エンジニアリング文化品質管理 の取り組みも取り上げる。 エンジニアやテクニカルライターによる 実践知見 を共有。

25ミリオン行のコードベースを一晩で整形:rubyfmtの舞台裏

  • Stripeの Developer Productivityチーム による大規模コード整形プロジェクト

  • 対象は 25,000,000行 に及ぶ巨大なRubyコードベース

  • コードの一貫性維持と 開発効率向上 を目的とした自動フォーマッタ rubyfmt の導入

  • プロジェクトリーダーは Fable Tales、テクニカルライターは Anna Mason

  • 一晩で全コードを自動整形するための インフラ設計運用ノウハウ

    • CI/CDパイプラインの拡張と 並列処理 による高速化
    • フォーマット前後の 差分検証 と自動テストによる品質担保
    • 開発者への 周知徹底 とドキュメント整備
  • 大規模リファクタリングに伴う 課題解決アプローチ

    • レガシーコードや独自記法への対応
    • フォーマットによる動作変更リスクの最小化
    • チーム横断での 合意形成 とルール策定
  • Stripeの エンジニアリング文化

    • 継続的改善(Continuous Improvement)の実践
    • コード品質開発速度 の両立
    • オープンな情報共有と ナレッジベース の構築
  • rubyfmt導入による 効果

    • コードレビュー負担の軽減
    • 新規開発・保守作業の効率化
    • 全社的な コーディングスタイル統一
  • 開発者向け追加リソース

    • Stripe Developers 公式YouTubeチャンネル
    • 開発者向けドキュメントとガイド
    • Stripe Discordサーバーや Developer Meetup によるコミュニティ支援

関連事例紹介

  • Selective Test Execution による50M行規模のテスト効率化
    • 必要なテストのみ実行することでCIの高速化を実現
  • Stripe CLIを使った 開発スタック自動構築
    • ホスティングやデータベース、AIツールの即時プロビジョニング

Stripeの情報・リソース

  • 公式ドキュメントや開発者向けガイドの活用推奨
  • YouTube、Twitter/X、Discordなどの公式SNSでの情報発信
  • 各地の Developer Meetup での最新情報共有とネットワーキング

著者情報

  • Fable Tales:StripeのDeveloper Productivity担当エンジニア
  • Anna Mason:Stripeのテクニカルライター、全社横断で執筆活動

Hackerたちの意見

25M行のコードにはビックリだよ!一つのコードベースにそんなに膨大な量があるなんて、全然想像できない。もっと詳しく知りたいな。

そうだね、残りのコードはどこにあるの?

記事によると、今は4200万行になってるみたいだよ。

たったの2500万行? :) Googleは10年前には数十億行あったのに… https://research.google/pubs/why-google-stores-billions-of-l...

うちの会社(Stripeよりはずっと小さいけど)は、今や450万以上になってるよ。グラフは完全に指数関数的だね。AIはここで大きな問題になってる。コードの量が爆発的に増えてるし、生成されるコードの質はまた別の話だね。

「一夜にして」っていう部分に驚いてる。Chromiumのソースでclang-formatを実行してみたんだけど(68,281の.ccファイル、wcによると2100万行):$ find chromium-149.0.7826.1/ -name ".cc" -exec cat {} + | wc 21640925 55715244 833460441 これが2014年のE5-2696 v3で6分もかからなかったよ:$ time find chromium-149.0.7826.1/ -name *.cc | parallel -j 16 clang-format $x>/dev/null real 0m5.666s user 1m13.964s sys 0m13.373s これは桁違いに速いね。特に、彼らが自分のワークロードを俺のようなポテトで動かしてないと仮定すれば。Rubyの構文ってC++よりそんなに複雑なの?それともツールの問題なのかな?

私の勘違いでなければ、モノレポだね。だから、25M LoCが単一のアプリにあるわけじゃなくて、彼らのサーバーサイドコードと共有ライブラリの(すべて?)が含まれているんだ。他にもいろんな言語が使われているよ。16年と何千人ものエンジニアがたくさんのコードを書くんだから。

swagger、protobuf、sqlcなどから生成されたたくさんのモデルやスタブを想像してみて。

浮いてるスパイラルのやつ、めっちゃ気が散って、記事を読むよりもInspectorでそれを消すのに時間かかっちゃった。読者を嫌ってるんじゃないかって思うくらい、ひどいよね。

prefers-reduced-motion: reduceを設定すれば、消えるよ。

初めての仕事は、小さなソフトウェア会社で、少数のクライアント向けにソフトを作ってたんだ。MS Basic PDSでね。リード開発者がコードのフォーマットを気にしないタイプで、だから「makenice」ってツールを作って、彼のぐちゃぐちゃなコードを見やすく整形したんだ。そしたら、彼はめっちゃ怒って、オフィスの前でぐるぐる回ってたよ。それで「makenasty」ってツールも作って、彼が好むスタイルにフォーマットするようにしたんだ。makenasty/niceはチームの数人にだけ共有したけど、みんな喜んでくれた。読みやすいものとリーダーが好きなものの間で簡単に変換できたからね。彼はmakenastyのことを全く知らなかったよ。

名前の問題を除けば、開発者の快適さのためにこれを行うのは全く理にかなったことだし、通常は簡単な変換で実現できるよ。手動で追加したインデントやスペースの調整などの制限があることが多いけど、どんな変更を許可するかをしっかり考えて、言語をよく理解していれば、非常に安全な操作になることができる。

もし彼がコードをフォーマットすることを気にしなかったら、彼が好むようにコードをフォーマットするツールを作るのは不可能に思えるだろうね。

みんなが醜い/不必要なdiffの痛みが小さなフォーマットの不一致の痛みより大きいと同意しているとき、解決できない衝突が多いと感じる。

こういう受動攻撃的なクソみたいなのが、テック業界の問題そのものだよ。人々は決断を下さない。ただ受動的に抵抗して、権威は情報の流れが断片的になって混乱するだけ。

rob pikeがgofmtのスタイルは「誰の好みでもない」って言ってたのを思い出す。

一気にリフォーマットすることにしたのは意外だね。週末にやっても、スケール的にたくさんのオープンPRに影響が出るだろうし。過去にいくつかの大きなコードベース(数十万から数百万行)でフォーマッターを導入したことがあるけど、いつもオープンPRで触れてないファイルを一括でリフォーマットするスクリプトを使って、段階的にやってたんだ。最初の実行で95%のファイルをリフォーマットして、その後約2週間毎日スクリプトを回して99.5%まで持っていった。残りの約12個のWIPのPRがマージされるたびに手動でやってたよ。

チームに知らせて、彼らのPRブランチでフォーマッターを適用できるようにするのもいいよ。

両方の選択肢には利点と欠点があるよね。何らかのラチェットを使えば、チームに気づかれずに進められるけど、今後のPRはリフォーマットがたくさんあって、git blameがめちゃくちゃになるよ。一気にやると、誰かがコンフリクトを解決しなきゃいけないけど、blame.ignoreRevsFileを使えば、履歴が役立つままでいられるよ。

おそらく、著者が他の人を批判できる立場だからだろうね(推測だけど、シニアプリンシパルの何かエンジニアだろう)。

一気にリフォーマットするとは驚いたよ。週末にやっても、彼らの規模だとオープンなPRがたくさん影響を受けるだろうね。PRのリベースは簡単なはずだから、ファイルを再フォーマットしながら全コミットを書き直して、リベースして、git checkout --theirs、再度フォーマッターを実行して、git rebase --continueすればいいんだ。手順が決まってるからスクリプト化もできるし、手動でコンフリクトを解決する必要もないよ。

私たちはマージコンフリクトを避けるために、土曜日にコードベース全体をフォーマットすることにしました。テストスイートのおかげで、すべてが正しく行われたという自信はあったけど、GitHubが表示できないほどの大きなdiffを見るのはちょっと怖いよね。dartフォーマッターには内部のサニティチェックがあって、未フォーマットとフォーマット済みの文字列を並行してチェックしながら、ホワイトスペースをスキップするんだ。もしホワイトスペース以外の文字が一致しなかったら、すぐに中止される。これでフォーマッターが変更するのはホワイトスペースだけに限定されるから、大きなコードベースで目を閉じて実行するのがずっと楽になるんだ。このサニティチェックは、変なバグが入り込んだときに何度か助けられたことがあるよ。特に新しい構文の周りの珍しい言語機能の組み合わせのときにね。(残念ながら、過去1年でフォーマッターは変更の柔軟性が増して、時にはコメントがカンマや括弧に対して相対的に移動することもあるから、このサニティチェックは一部の句読点をスキップするようになって、ちょっと信頼性が下がってる。)

もっと豪華なバージョンだと、抽象構文木を比較することになるのかな。

厳密に言うと、それはうまくいかないよ。例えば、a1とa 1は違うからね。

たくさんのフォーマッターは、末尾のカンマみたいなものも統一するから、もうちょっと手間がかかるかもね。

その複雑さを考えると、仮説はシンプルだった:最も難しい構文から取り組んで、残りは後からついてくる。いつも見るのはいいね。人々が一般的なケースのために設計する罠に陥るのを見たことがあるけど、実際にはほとんどのコードはあまり一般的でないケースに対処するためのものだって気づいていないんだ。

別の分野では「急所を狙う」って呼ばれているよ。その鮮やかな表現がポイントをうまく伝えてる。何かをマスターしたいなら、一番難しい部分を理解しなきゃいけない。だから、まずそれに取り組んで、あとは簡単になる。すでにその分野をマスターした人として対処しているからね。

タブにそんなに時間をかけられるなんて、いいなぁ。

クリーンなインデントは、時間を節約するためのものだよ。意味不明なコードを理解しようとして長時間迷ってしまうのを避けるためにね。結局、無秩序なインデントに隠れた普通のバグだって気づくまで。

これがRustでの書き直しになるって分かってたよ。

なんでこんな大規模なマージをする必要があったのか理解できない。フォーマッターなんだから、ファイルは前後で機能的に同じであるべきじゃない?新しいファイルや編集中のファイルに一時的に適用して、慣れてきたら古いファイルにバッチで適用すればよかったのに。大規模マージの利点って何なの?同じリワードに対してリスクが高すぎる気がする。

微妙なバグを引き起こす可能性があるから、みんなの頭にあるうちに、できるだけ包括的なレビューとテストをして満足させる必要があるよ。一気にやらないと、同じテストを何度も繰り返さなきゃいけなくなるからね。>「こういうことを言うとき、前後でファイルは機能的に同等であるべきだ。あなたが進んでいる道は善意で舗装されている。」

原則的には、パースツリーを保存して、それをgitのようなもので公開することを妨げるものは何もないってことを思い出させてくれるね。そうすれば、フォーマットを必要としなくなるし、そのフォーマットに基づくマージコンフリクトの一カテゴリを解決する必要もなくなる。フォーマットって、データに対するテーマみたいなもんだからさ。つまり、コードだね。