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

コードレビューはもっと良くなることができる

概要

  • GitHubのコードレビュー体験に不満を持つ開発者の視点
  • git-reviewツールによるローカル重視のレビュー実験の経緯
  • コメントをコミットとして管理するアプローチの利点と課題
  • 実装面での摩擦や運用上の問題点
  • 今後の展望と関連プロジェクトの紹介

GitHubのコードレビュー体験への課題意識

  • 多くの人が GitHubのコードレビュー プロセスに不満を持つ現状

  • Stacked Pull Requestsinterdiffレビュー が十分にサポートされていない問題

  • レビュー状態が リポジトリ自体に保存されない ことへの不満

  • Webインターフェース中心 のリモートレビュー体験が主流

    • Jane Streetのような ローカル中心のワークフロー の例外

ローカルでのレビュー体験の理想

  • コード執筆同様、 レビューもローカルで完結 させたいニーズ
  • ローカルで ブランチをチェックアウト し、差分や本体コードを自在に閲覧
  • magit などのツールで効率的なレビュー操作
  • gitのステージングエリア を使い、既読ファイルの管理
  • テスト実行やリファクタ提案 など、自由度の高いレビュー作業

GitHubのWebレビューの不便さ

  • フィードバック投稿時に Webブラウザを開く必要 がある手間
  • HTTPラウンドトリップ による遅延や、大きな差分での テキストエリアのラグ
  • レビューコメントの インライン性即時性 が損なわれる問題

git-reviewツールの発想と実装

  • レビュー内容を1コミット としてPRブランチの先頭に追加する設計
  • 特定マーカー付きコメント でレビュー内容を管理
  • レビューは 著者とレビュワーがコミットを直接編集 して進行
  • 全スレッドが//? resolvedで解決されたら リバートコミット を追加し終了

git-review運用上の課題

  • レビューコメントとコード修正の競合 発生リスク
  • deep commitや新規コミット追加時の コメント位置の衝突問題
  • --force-with-lease 運用の煩雑さ
  • コードでは厳密な履歴管理が求められる一方、レビューでは 緩やかなマージ規則 が理想
  • コメントのpush/popを自動化するには 複雑なツール化 が必要

今後の展望と他のアプローチ

  • GerritスタイルのChange-Id がgit本体に導入される可能性

    • これにより コミット単位のinterdiffレビュー が実現する期待
    • git-reviewのアプローチとは 部分的に非互換
    • Change-Id単位で 全バージョンの履歴管理コメント追加 ができる可能性
  • 現時点では Webインターフェース型レビュー に戻った現状

  • より良いレビュー体験 の実現に向けた他プロジェクトの紹介

    • Fossil: 全てをリポジトリ内に保存 するSCM
    • GerritのNoteDb: レビュー状態をgit内で管理
    • git-bug: イシュー情報をgitで管理
    • git-appraise: レビュー情報をgitで管理
    • prr: エディタ内レビューインターフェース をGitHub API上に実装
    • How Jane Street Does Code Review: 理想的なレビュー体験の実例

結論

  • git-reviewアプローチは 部分的には有効 だが、運用や実装の摩擦が大きい
  • コードレビュー体験の革新 には、今後も新たなツールや仕組みが求められる状況
  • ローカル主体のレビュー体験リポジトリ内状態管理 への挑戦は今後も重要な課題

Hackerたちの意見

コードレビューにgitを直接使うアイデア、めっちゃ魅力的だと思う。自分が変更したものをローカルで扱えるのは、クソみたいな読み取り専用のウェブUIを考えるとすごく便利だよね。なんでレビューが単一のコミットである必要があるのか、全然わからない。git-reviewの実装をシンプルに保つため? それとも、レビューする人がみんな自分のコメントや修正をPRブランチに直接コミットするアプローチがうまくいくんじゃないかって思う。そうすれば、便利に作業できるための追加ツールもいらないかもしれないし。このアイデアは、従来のGitHubフローと、Linux開発がメーリングリストやパッチを通じて整理されている方法のハイブリッドみたいだね。

GitHubのPRって読み取り専用って考えられてるの? チームメンバーが「提案」として修正を編集して、俺のブランチにコミットとして追加するのを承認できるんだけど。

なぜレビューが単一のコミットである必要があるのか理解できなかった。 うん、それは確かに変だね。5人が私のコードをレビューする場合、みんな同じレビューコミットをいじるの?コードでもそんなことはしないし、意味がない気がする。レビューは、レビューされたコミットの上にコミットが必要だよ。同じコミットのレビューが5つあれば、みんなそのコミットから分岐することになる。そして、それに対処するためには、別のコミットが必要で、それも彼らの横に存在することになる。各コミットの変更プロセスは、スタックされたコミットが互いに連なったブランチになる。連なったブランチの各コミットには、コメントコミットが添付されることになる。もし議論があれば、そのコメントコミットもチェーンを形成するかもしれない。みんなが満足したら、各ブランチは一つのコミットにまとめられ、それがメインブランチにリベースされることになる。ただし、議論をしばらく保存するために新しいコミットを作る方がいいかもしれない。それが重要なポイントだね:そのデータはメインブランチの外に存在するけど、どこかに存在する必要がある。

コードをレビューする時は、ソースブランチをローカルにプルするのが好きだ。そしたら、コードをベースにソフトリセットして、自分が書いたみたいに見せる。これは、明確なコミットセットがない大きな変更をレビューする時と eerily似てる。実際の問題は、作業を小さく自己完結した単位に分けないことに気づかない人たちと一緒に働くことだ。誰もが、たくさんのファイルに対する大量のdiffをレビューして、本当に理解できるとは言えないよ。全体は部分の合計以上のものだからね。

簡単に使えるボタンが欲しい人向け。これだよ。

review () {
  if [[ -n $(git status -s) ]]
  then
    echo 'クリーンなツリーから始める必要があります!'
    return 1
  fi
  git checkout pristine # コミットしないブランチ
  git rebase origin/master
  branch="$1"
  git branch -D "$branch"
  git checkout "$branch"
  git rebase origin/master
  git reset --soft origin/master
  git reset nvim -c ':G' # fugitiveプラグイン付きのneovimを開く - お好みのエディタに置き換えて
  git reset --hard
  git status -s | awk '{ print $2 }' | xargs rm
  git checkout pristine
  git branch -D "$branch"
}

良いコミットを作り、そのコミットから良いPRを作るのは、良いコードを書くのと同じくらいのスキルだよ。残念ながら、前者が苦手な人が多すぎる。

私たちは、高度に並列化可能なデータサイエンスのタスクを持つチームで、リポジトリのコピーを3つチェックアウトしたよ。1つは自分たちのブランチ用、残りの2つはレビュー担当のブランチ用。

これ使ってるよ: https://github.com/sindrets/diffview.nvim をneovimのPRレビュー用ツールとして。基本的にvscodeのdiffツールのUIに似てるけど、vimの内蔵diffモードと統合されてる。あと、git log -p --function-contextも、あまり関与しないレビューにはすごく便利。

一歩引いて考えると、HNでスタックプルリクエストについて読んでるのがめっちゃクールだと思う。数年前にgraphite.devを始めた時、これはほとんどの開発者がFBやGoogleにいた時以外は聞いたこともないワークフローだった。3〜4年でコードレビューがどれだけ早く変わるかを見るのは楽しいね :)

そうだね、マジで!

figワークフローが恋しいな :-(

自分はプレ・マーキュリアルのアーカニスト難民で、まだメガPRやマージコミット、他のGitHubの問題で苦しんでるチームにGraphiteを推してる。多少ゴタゴタしたスケーリングの道を歩んでるけど、全体的には大ファンだよ :) PRとの相互運用性を持たせることから生まれる野心と結果には本当に感謝してる。これは悪夢のような問題だけど、うまくいってるだけでもすごいし、ほとんどの時間うまくいってるのはもっとすごい。Graphiteがリポジトリをハードコアな設定で初期化して、基盤となるリポジトリについてもっと多くの仮定をできるような処方的モードを強く推奨したい。マージコミットとか、リストは俺よりもよく知ってるだろうけど。これがあれば、バレットプルーフになれると思う。

友達、Graphiteが大好きだ。最高のAIコードレビューだよ、間違いなく。(いくつか試したけどね。)

登録したばかりだけど、今まで欲しかったものが全部揃ってるみたい!

すでにタスクを原子(またはそれに近い)単位に分けている人として、これは各コミットごとにPRを提出するってことなの?ブレイキングチェンジの時はどうなるの?フィーチャーフラグを使う必要があるの?

CodeRabbitで起きたセキュリティインシデントを考えると、LLMと自分のコードベースを同じツールで使う新しいツールを試すのにはあまり気が乗らないな。新しいことを試すのはとても良い実験になり得るけど、簡単にセキュリティの頭痛の種になっちゃうからね。

スタックされたプルリクエストって、最初から避けられるはずの問題に余計な複雑さを加えてる気がする。頻繁に小さな変更をするのが本当に良いプラクティスだよね。それに、トランクベースの開発や継続的インテグレーションみたいな考え方もあるし。

Gitがファーストクラスの変更IDを考えてるなんて、すごくクールだね!! これは、FacebookでPhabricatorのdiffでリビジョンを追跡してたのに似てる。これについて読むのに最適な場所を知ってる人いる?

根本的な問題は、gitがブランチをまともに追跡していないことだと思う。これを修正した方がいいんじゃない?Fossilは、コミットがどのブランチで行われたかを覚えているから、タスクブランチ自体が変更IDになる。それを解決するのは難しいかもしれないけど、gitコマンドが履歴をいじることを許可しながらね。Fossilにはその問題はないけど。

Githubで一番イライラするのは、アプリがめっちゃ遅いこと。遅いって言うのは、ブラウザのタブがフリーズするレベルの遅さね。驚くことに、今まで使った中で一番いいコードレビューのツールはAzure DevOpsだった。

ニット:gripじゃなくてgripeね :-P

Githubで一番イライラするのは、アプリがめっちゃ遅いこと。遅いって言うのは、ブラウザのタブがフリーズするレベルの遅さね。大規模なJavascriptと、早く動かなきゃいけないチームが組み合わさると、こうなるのは仕方ないよね。少なくともAtlassianじゃないけど。

Microsoftの会社で働いてた時、Azure DevOpsを使ってた。正直、.NETのことに関しては悪くないよ。Visual StudioがC#にぴったりなように、.NET開発のライフサイクルに合ってる。

DevOpsの何がそんなに良かったの?毎日使ってるけど、レビューシステムに問題はないし、個人的にはGithubとすごく似てると思う。むしろ、変更を提案して、誰かがボタンをクリックしてコミットとして統合してくれるのが恋しいな。

コードレビューでずっと気になってたのは、役に立つことをほとんどがプロセスの後半でしか指摘しないってこと。主観的な小さなことを指摘するのはどうでもいいけど、実際に役立つ結果が出るのは、ほとんどの場合、全てを最初からやり直す必要があるか(全く新しいデザイン)か、そもそもやるべきじゃなかったってことが分かって放棄されるかのどっちか。コードレビューが唯一、全ての関係者が本当に関与して変化を考え始めるタイミングみたいに感じる。Jiraのチケットやミーティングで早い段階で少し話し合いがあったり、運が良ければ誰かがデザイン仕様書を書いたりすることもあるけど、他のチームや組織の遠いところにいる人は、コードレビューを見て初めて変更を知ることが多い。私もそう。別のチームが何かバカなことを実装したのに気づくのは、突然コードレビューの通知が来るから。これをどうにかする方法は分からないけど、会社全体の全員が近い将来に開発されるかもしれない全てのことを見ているわけにはいかないよね。できるのかな?分からないけど、そんなことはまず起こらない気がする。90年代の大学で開発プロセスについての授業を受けたときは、コードレビューだけじゃなくてデザインレビューもあったけど、実際にはそんなものに出会ったことはない(正式な意味で)。デザインレビューのプロセスが、何かを実装する前に捕まえたいことを全てキャッチできるかどうかも分からない。

それは、私が実際に出会ったことがない(正式な意味で)から。ソフトウェアエンジニアリングの世界では、あまりエンジニアリングが関与していないからね。とはいえ、業界は様々な理由から、適切なエンジニアリングプロセスの遅さを受け入れたがらないと思う。ほとんどのソフトウェアが重要でないことや、バグやエラーをその場で修正できる可能性があるから。他のエンジニアリング分野にはそんな贅沢はない。橋は列車を支えるか、支えないかだし、製造工場をうまく作らなければ修正の余地はほとんどない。飛行機のエンジンも、動くか動かないかのどちらか。リスクや修正の機会が異なるから、実践も違ってくるんだ。

あなたの組織は構造とコミュニケーションが不足しているみたいだね。私の職場では、コードベースのほとんどの部分に責任を持つチームがあって、そこで大部分の変更を行っている。もし「外部者」が変更を計画する場合は、チームに話をしに来て調整するんだ。そして、チーム内のコミュニケーションも強い。誰が何をやっているのかが明確で、チーム内で「どうするか」を合意するためにデザインレビューもある。あなたが言っているようなことは滅多に起こらないよ。私が行うコードレビューの95%は、コメントなしか、ほんの少しの改善提案だけ。主に、事前に大事なことについて話し合う文化ができているからで、コードを書くのはプロセスの最後のステップに過ぎない。チーム内である程度一貫したスタイルも確立されているよ。チーム間で必ずしも同じではないけど、それでも大丈夫。要するに、あなたが経験しているよりも良い方法で物事を進めることは確かに可能だよ。構造、コミュニケーション、文化の問題だね。

私の職場では、基本的なデザイン決定のためにRFCを書くことが多い。何が「基本的なデザイン決定」と見なされるかは、その場で自己調整されることもあるけど、長期的な計画を立てるときにも考慮している。例えば、Jiraでエピックを最初に作成する際、どうアプローチするかが分からないことが多いから、RFCを書くタスクから始めることがある。これらは私たちのチームだけのために書かれることもあれば、他のソフトウェアチーム全体の目に触れるために書かれることもある。後者の場合、2週間ごとのミーティングで議論のためにRFCを提出するんだけど、事前に告知しておくから、みんなが読んでコメントを残せるようになっている。興味のあるRFCが議論されるときだけミーティングに出席すればいいんだ。これが私たちにはうまくいってる!時々、これを書くのが面倒に感じることもあるし、過剰に使っている気もするけど、他の職場で全く協力的なデザインプロセスがなかったところよりは、私たちのアプローチの方がずっと好ましいよ。

これを解決する一つの方法はペアプログラミングだね。コードを書くときにリアルタイムでフィードバックがもらえる。ただ、うまく機能する条件を整えるのが難しいこともある。興味を持っている人たちがいて、似たようなスケジュールを持っている必要があるし、テストが走るのを二人で待っているのは避けたいよね。

「それをどうやって解決するかは分からないけどね。会社全体の全員が、近い将来に開発されるかもしれない全てのことを見てるわけにはいかないよね。でも、できるのかな? そんな必要もないと思う。以下のような組み合わせでうまくいくことが多いよ。 1. システムを分離して、組織の一部の人が遠くの部分に悪影響を与えるような変更をしにくくする。 2. 何らかのデザインレビューのプロセス:もっとスピードを重視するなら「さっとRFCドキュメントを書いて、関連チームに共有する」みたいな感じで、整合性を重視するなら「デザインドキュメントを書いて、デザイン委員会で正式にレビュー・承認を受ける」ってこともできる。 3. システムやコードベースについて広いコンテキストを持っている人たちのグループ(通常はシニアや経験豊富なエンジニア)。これも正式に「デザインレビュー委員会があります」とか、あまり堅苦しくなく「この人たちに相談してみて」みたいな感じでもいい。うまくやれば、こういうグループからかなり広いカバレッジが得られると思う。「会社全体の全員」ってわけじゃないけどね。そのグループが他の人を引き込んだり、方向を変えたりもできるし。 4. プロセスには少しのロスがあることを受け入れる。レビューアを見逃すこともあるし、実装を始めると実際の状況が人々の期待とは違うこともあるから。これに対処するために、POCやドラフト実装、スパイクを奨励して、全てのコードが本番に入るわけではないという期待を設定することができる。創造的なプロセスにはドラフトやリライトが含まれるから、最終的な形に至るための探求を助けるんだ。こういうのは、5人のエンジニアから数千人規模の会社まで、かなりうまく機能するのを見てきたよ。」

「コードレビューの時だけ、全てのステークホルダーが本当に関与して、変更について考え始めるように見える。それはGitやバージョン管理システムの問題じゃなくて、あなたの組織の問題だよ。PRはそれとは無関係だ。PRに立ち寄って、そのPRを生んだチケットやそのチケットの作成に至るまでの全ての議論や決定プロセスを知らないままだと、ループから外れてるよ。あなたの不満は、印刷プロセスが欠陥だと文句を言う出版社のようなもの。印刷された本が生産ラインから出てくるのが、全てのステークホルダーが本当に関与する唯一の瞬間だから。機能不全の会社で働いている場合だけどね。」

その気持ち、わかるよ。デザインレビューについては、今の職場でもやってたけど、プロトタイピングや反復デザインの方が良いってことで、正式なデザインドキュメントやデザインレビューをやめたんだ。デザインフェーズの問題は、重要な詳細を考慮しきれないことが多いこと。いろいろ話し合って、実装に入った時に重要な詳細や洞察を見落としていることに気づくことが多かった。でも、デザインフェーズにかなりの時間を投資したから、ショートカットを取りたくなるんだよね。さらに、デザインレビューはチーム全体で行われることはなくて、10人以上が同じ部屋にいるのは逆効果だから。だから、コードレビューの時にまだ発見があるんだ。そして、みんなが良いデザインドキュメントを作るのが得意だったり、モチベーションがあるわけでもない。結局、5人以上の開発チームはこういう非効率に直面する運命にあると思う。理想的なのは、POと数人のキーユーザーと一緒に5人を同じ部屋に入れることだね。

私は小さなチームで働いていて、基本的に4〜6人のコア開発者がいます。新しい機能を開発する時は、まず頭の中でざっくりとアプローチを考えたら、同僚と話し合うようにしています。みんなも同じようにしているので、コードレビューでは主にちょっとしたコードの匂いとかだけで済むことが多いです。でも、アーキテクチャについては一緒に決めることが多いですね(だいたい2〜3人で)。これが私たちのチームにとって一番大事なことだと思っています。もしみんながコードについて意見が合わないと、誰も自分が書いてないコードに関わりたがらなくなって、全体がうまくいかなくなっちゃいます。大きな組織でも、責任が適切に分担されていれば、こういうやり方はまだ可能だと思います。ただの4人が全てを知っていて、他の40人がその人たちに依存しているような状況じゃなければね。

読むのがとても楽しいね。Sourcehutがリストに入ってないのが残念だけど、あれはパッチやバグ、ディスカッションスレッドをメールで送るという古典的なコンセプトに基づいて作られてるんだ。メールリストやCIソリューションにこのコンセプトを組み込んで、CIのステータスやログもメールで送ってくれる。Drew Devaultが、git-send-email.ioやgit-am.ioでパッチをメールで送ったりレビューしたりするための役立つリソースを公開してるよ。

これが、僕が新しいジュニア開発者だった頃にコードレビューを学んだ方法だよ。別のジュニアのコードにレビューコメントを書いて、そしたらチームリーダーが見落としたコメントを書いてくれて、二人のジュニアがそれを読んで何を見逃したかを確認するって感じ。コーディングやレビューについて学ぶにはいい方法だと思う。

これについて考えていた代替案があるんだけど、1人がコードを書くのではなく、最初の草案を書いて、別の人がそれを調整してマージするっていうのはどうかな?逆もありで、役割を交代する。誰かこんなこと試したことある?どうだった?

それって基本的に非同期のペアプログラミングセッションだよね?

ずっと気づいてたんだけど、レビュー対象のコードを書くことに参加していると、もっと多くの洞察を提供できるんだ。君が提案しているのは、コードを「私たちのコード」と考えることから始まっていると思う。そうすると、プルリクエストでよく起こる「私のコード」と「君のコード」の対立がなくなるしね。最初から完璧を目指すのではなく、反復的に作業することを学ぶのが大事で、TDDのような手法とも相性がいいと思う。

いいアイデアだね、でも開発時間が2倍かかる覚悟があるなら。

ペアプログラミングでトランクベースの開発をしてる人たちを知ってるよ。コードを一緒に書いて、満足したらメインブランチにマージして、テストが通れば本番にデプロイするってやり方。彼らにはうまくいってるみたい。

lubenoのPR実装に取り組んでいて、コードレビューのプロセスについてたくさん考えてる。大きな問題は、各チームが少しずつ異なるワークフローを持っていて、ルールや要件も違うこと。GitHubの構造は、GitHubチームの働き方の結果なんだ。彼らは「PRにコミットを追加し続ける」というワークフローで、自分たちにとって最適なツールを作ったんだよね。みんなの既存のワークフローに合わせてツールを柔軟に適応させる必要があるか、もしくは自分たちのワークフロー(GitHub)にこだわって、みんなをそのやり方に合わせる必要がある。ほとんどの場合、これがうまくいくのは、人々が「最善のやり方」を知りたがっていて、最適なワークフローを考える時間を無駄にしたくないからだよね。