私のいちばん好きなGitコミット(2019)
(dhwthompson.com)- GOV.UK の作業中に生まれた Dan Carley の「“Convert template to US-ASCII to fix error”」コミットは、小さなコード変更でも 詳細なコミットメッセージ によってコードベースの長期的なドキュメントになり得ることを示している
- 良いメッセージは何を変えたかよりも、なぜ変えたのかを残し、この事例では
bundle exec rake実行時に発生したinvalid byte sequence in US-ASCIIエラーと再現条件を具体的に記録している - エラー文字列と調査過程が一緒に残っているため、後から同じ問題に遭遇した人が
git log --grepや GitHub のコミット検索で 過去の解決記録 を見つけられる - メッセージには
find -exec、file --mime、iconvのような Unix ツールの利用過程も含まれており、レビュー担当者や後の読者が問題解決の流れを追える - すべてのコミットがこの程度の長さである必要はないが、文脈・調査過程・感情まで残したメッセージは、チームの 共有されたコードベース理解 と信頼を築く助けになる
小さな変更を長期ドキュメントにしたコミット
- Git のコミットメッセージは、うまく書けばコードベースの寿命を通じて残る 強力なドキュメント化ツール になり得る
- 例として挙げられているコミットは、Government Digital Service で GOV.UK に取り組んでいた時期の変更である
- 作者は Dan Carley で、タイトルは「Convert template to US-ASCII to fix error」である
- GDS の公開開発スタイルのおかげで、このような組織内部の事例も外部に共有できる
長さより重要な文脈
- このコミットはコード変更量に比べてメッセージが長いが、核心的な価値は長さそのものではなく 有用な文脈 にある
- 同じ変更でも別の場所なら
change whitespaceやfix bugのように短く残されていたかもしれない - Dan は周囲の同僚や後の読者が問題の原因と解決過程を理解できるよう、詳細な記録を残している
何が変わったかより、なぜ変わったか
- 良いコミットメッセージは、何が変わったかだけでなく なぜ変わったか を説明する
- このコミットは、feature branch で
/etc/nginx/router_routes.confの内容と突き合わせるテストを追加した後、実行方法によって結果が異なっていたことを記録しているbundle exec rake specまたはbundle exec rspec modules/router/specで実行すると正常に動作するbundle exec rakeで実行すると各shouldブロックが失敗する- エラーは
ArgumentError: invalid byte sequence in US-ASCIIである
- こうした説明がなければ、どのツールでどのようなパースエラーが起きたのか推測しなければならなかった可能性が高い
- 元の作業文脈は、人が忘れたり、チームを移ったり、組織を離れたりすることで簡単に失われうる
検索可能なエラー記録
- コミットメッセージの冒頭には、変更のきっかけになった エラーメッセージ がそのまま入っている
- 同じエラーに遭遇した人は、次の方法でコードベース内の過去の記録を探せる
git log --grep "invalid byte sequence"- GitHub commit search
- 検索結果を見ると複数の人がこのエラーを検索しており、誰が最初に問題に遭遇し、どのような対応を取ったのかも確認できた
問題解決の過程を物語として残す
- メッセージは、問題がどう見えたか、どの順番で調査したか、最終的にどう修正したかを追えるように構成されている
- 例として、
.with_content(//)matcher を取り除くとエラーが消えたこと、spec ファイルにはおかしな文字がなかったこと、同じインタプリタで Puppet を require すると再現できたことなどが含まれている - コミットメッセージは特定のファイル・関数・コード1行ではなく 変更そのもの を文書化するため、コードベースがたどってきた道のりを記録するのに適した場所である
チームの知識を広げる副次効果
- Dan は調査の各段階で実行したコマンドを記録しており、これはチーム内で知識を軽やかに広める方法にもなりうる
- 読者はコミットメッセージだけでも Unix ツールの使い方を学べる
findに-exec引数を渡すと、見つかった各ファイルに対してコマンドを実行できる- コマンドの末尾に
\+を付けると、ファイルごとに1回ずつ実行する代わりに、複数のファイル名を1つのfileコマンドに渡せる file --mimeはファイルの MIME タイプを教えてくれるiconvというツールが存在する
- 変更をレビューする人だけでなく、後からこのコミットを見つけた人も同じ情報を得られる
- 十分な時間とコミットが積み重なれば、このようなメッセージはチームの 知識増幅装置 になりうる
共感と信頼を築く記録
- コミットの最後には「Now the tests work! One hour of my life I won't get back..」という一文がある
- この文は、1時間にわたって巧妙なバグを追跡した開発者の苛立ちと、解決したときの満足感を伝えている
- 短期的なハックやプロトタイプのコードが本番環境に入り込み定着してしまった場合でも、このようなメッセージは、あらゆる変更の背後に、その時点で得られる情報の中で最善の判断をした人がいたことを思い出させてくれる
すべてのコミットが長くある必要はない
- この事例は極端な例であり、とくにこの規模のすべてのコミットに同じレベルの詳細さを期待する必要はない
- それでも、変更の背後にある文脈を説明し、他の人が学べるようにし、チームのコードベースに対する 共通のメンタルモデル に貢献する好例である
- 良いコミットメッセージや変更の構造化ツールに関心があるなら、次の資料も参照できる
- Telling stories through your commits by Joel Chippindale
- A branch in time by Tekin Süleyman
1件のコメント
Hacker Newsの意見
良くも悪くも、GitHubの共同創業者であり『Pro Git』のようなGit本を書いた立場から見ると、Gitのコミットメッセージはコードを文書化する独特の経路だが、非常に非効率でもある。
核心的な問題は、GitやGitHubなど大半のツールが通常最初の1行だけを表示することだ。例のコミットも「US-ASCII error」のような一般的な1行しか見えず、記事で称賛されている残りの内容は、現代のツールではほとんど誰も見ない。
Gitはコミットメッセージを、プロジェクトの全員が読むメール本文として設計したが、今日では概ねその役割を果たしていない。メーリングリストでパッチシリーズとして議論されるのでない限り、タイトルの最初の50文字以外はほとんど読まれない。
git blameで-w -C -C -Cなのか-Cが2つなのかを調べて関連メッセージを追跡しても、最初の1行しか見えず、結局SHAを見つけてgit showしなければ長いメッセージは見られない。うまく書かれていても情報を再発見するのが難しすぎる、というのがGitに対する最大の不満の一つだ。Gitプロジェクトの履歴を見ると、コミットメッセージはほぼ毎回すばらしいが、1つのファイルに
blameをかけても関数実装の文脈を追うのは非常に難しい。Jeff Kingがこの10年間に残してきた文書化は天才的なレベルなのに、ほとんど誰も味わえないという点がひどい。正確な解決策は分からないが、大半のコミュニティでは、コミットメッセージで優れた文書を書くことは、悲しいことにほとんど時間の無駄に近い。見つけるのが難しすぎるからだ。
git blame、git log、git showを常に使っているし、ファイルの履歴を追うのは簡単で、git log -Gで追加・削除された時点を見つけるのにも数秒で十分だ。つらいのは、コミットを見つけたのにメッセージが「bleh」や「add a thing」だけだったときだ。開発者が60秒だけ使って、なぜそうしたのかを書いてくれていればよかったのに、と思う。
逆に、ある変更がなぜ行われたのかを詳しく説明したコミットメッセージを見つけると本当にうれしい。良いコミットメッセージ1つで、数時間、数日の作業が節約できる。
GitHubも悪いコミットメッセージ問題に加担している。運が良ければPRの説明に詳細があるが、コミットログのすぐ横にあるわけではなく、たいていPRはJiraリンクへ、JiraはSlackの会話リンクへ、SlackはGoogleドキュメントのリンクへと続く。
業界は文書化が本当に下手だが、Jeff Kingのような人たちはきちんと戦っている。結局これは技術の問題ではなく、人の問題だと思う。文書を書くことには即時の報酬がないため追加作業のように感じられ、その報酬は数日、数週間、数カ月後になって初めて現れる。
どうか良いコミットメッセージを書いてほしい。なぜやったのかだけでも1分かけて書けば、すべてのコミットが忌々しいチェスタトンの柵の謎解きにならずに済む。簡単に見つけられるコミットメッセージに入れておけば、未来の自分と私が感謝する。
付け加えると、コミットメッセージは見つけにくいという主張にも同意しない。コードの1行、関数、ファイルの歴史を追うのにほとんど困難を感じないし、コミットメッセージは後で読まれなくても、書いた時点で価値がある。良いメッセージを書くと、自分が意図したコードを実際に書けているか確認することになるし、コードレビュー担当者にとっても価値がある。
git blameのオプションを見つけにくいという話も冗談のように聞こえる。優れたIDEなら各行にblame情報を付け、ボタン1つで差分を見せ、その差分内の文脈行や削除された行について再帰的にblameできるべきだ。Tigのようなツールはまさにそれをやってくれる。GitHubがコミットメッセージを見にくくしていることは認める。
コミットに付いた文書は、楽しみで書いているわけではない。後で存在しないかもしれない人から送られたパッチを受け入れるリスクを減らし、関連する考えを明らかにして他の人がその上で作業できるようにする仕組みだ。考えを隠すとコードに対する排他的な所有権が積み上がり、たいてい良いことではない。コミットメッセージは作業証明の役割も果たし、パッチが多すぎるときには重要になり得る。商用プロジェクトでは、その重要性の一部は低いかもしれない。
こうした文書を置く場所は、実際ほかにない。コードコメントには合わない。ところが今では、GitとはすなわちGitHubであり、Gitの唯一の目的はGitHubに変更をアップロードすることだ、と考える開発者世代が生まれている。
Gitは良くないが、他のものよりはずっとましだ。もう一度基本に立ち返り、バージョン管理の本当の目的を理解する必要がある。
だから現在のコミュニティには大きな助けにならないかもしれないが、数年後にデバッグする人には役立つ。
全体的な趣旨には同意するが、もっと具体的な BLUF を先頭に置くとよいと思う。たとえば「Fix test issues caused by non-breaking space character \xa0」のように書く、ということ
何が問題なのかすぐ分かるし、もっと知りたければ下を読む自由も残っている
だから履歴検索用には、良い 1行目のメッセージ だけで十分だと思う。バグの根本原因を探しにナルニアまで行って帰ってきた短い話は、あまり関係ない。ただし、PRの説明やコミットの拡張メッセージに、憂さ晴らしも兼ねて書くことまで反対するわけではない
優れたコミットメッセージを書くことへの誇りは感じたことがあるが、それが他人に与える価値についてはあまり確信がない。奇妙なエラーメッセージに遭遇したとき、新機能を追加するとき、あるいはほとんどどんな場合でも、多くの人はコミットメッセージを検索しないと思う
少し悲しいが、美しいコミットメッセージにはプログラマーの 虚栄 に近い側面があるのではないか、という疑いが強くなっている。主に感嘆するのは書いた本人で、他の人は気づかないまま通り過ぎる
ときにはそうした美的な装飾が入る余地はあるが、実用的価値が大きいとは確信していない。他人が「fix whitespace issue」とコミットしても、今ではあまり気にしなくなったし、そのおかげでより良い同僚になれた気がする
GitやLinuxのように巨大な分散チームと膨大なコミットがあるプロジェクトでは違うかもしれない。自分が慣れているプロジェクトはコントリビューターが1〜100人で、ほとんどが同じ組織に属している
GoogleやStack Overflowで答えが出ないときは、GitHubで似た内容がPRやコミットメッセージにあるか探すこともある。アクセス可能な非公開リポジトリまで含めて、数えきれないほど助けられてきた。良いコミットメッセージには、虚栄を超えた明確な価値がある。多くの開発者が見ないのなら、それは彼らの損失であり、経験を積めばいつか気づいてほしい。ジュニア開発者に検索方法を教えたり、元記事を送ったりするのもよい
それ以来、少なくとも未来の自分が変更が必要だった理由を思い出せるように、コミットメッセージを書くようにしている
git blameをしないなら、それは間違っている明らかなバグを直そうとしてコミット直前に以前のコミットを見ると、その「バグ」は意図的に入れられたもので、リンクされたJIRAの作業には、なぜ自分の明白な変更が2年前のバグを再発させることになるのかがよく説明されていた、ということが何度もある
git blame機能で、何が起きていたのかをよく把握している。良いコミットメッセージを見つけるとありがたく感じるコミットメッセージの1行目は、
git logが チェスタトンの柵 を扱えるようにするものなので最も重要だ。この場合、筆者は空振りしていると思う1行目には何をしたかではなく、なぜしたかを入れるのが肝心だ。何が変わったのかは、関心のある人がコードや差分を見ればよい
たとえば「nginx .conf files must be in us-ascii」と書き、その次に「changed blahblah.erb to remove nonbreaking space character」を置き、その後にかなり良い残りのコミットメッセージを続ければよい
ニュース記事のように考えるべきだ。読者がどの時点で読むのをやめてもよいと仮定し、重要度は下がり、詳細は増える順に書くべきだ
ニュース記事も見出しでは「なぜ」ではなく「何」を説明する
この場合、元の1行目はぴったりだ。
git logをざっと見ると、このコミットがテストの機能的な振る舞いを変えたものではない可能性が高いと判断して、読み飛ばせる「nginx .conf files must be in us-ascii」はバグやPRのタイトルとしてはよいかもしれないが、複数のコミットがそれぞれ別の作業に対応することもあり、実際に何が起きるのかは教えてくれない。ファイルをUS-ASCIIに変換するのか、変換ツールを作るのか、ドキュメントを更新するのか、テストを作るのか、あるいはその組み合わせなのか分からない。なぜではなく 何 から始めることで、その混乱を減らせる
「The files must be in us-ascii」でも「Changed the files to us-ascii」でも大差はない。どちらもファイルがUS-ASCIIに変わったことを明確に伝えている
このようなドキュメント化の欠点は、一度書いたコミットメッセージを事実上変更できないことだと思う。
もちろん
rebaseで可能ではあるが、1年前に書かれてすでにmainにマージされた内容を、本当に変更して全員の機能ブランチに苦痛を与えるのか、という気がする。.mdファイル、Wiki、Confluence に保存されたドキュメントと比べると、同僚が書いた内容を自分が改善でき、別の同僚も自分が書いた内容を改善できる。この事例ではバグが修正され、再発しないかもしれない。だが特定のコンポーネントをコミットするとき、その設計を説明したくなる誘惑があるが、今では避けている。ビジネス要件の変更などで後からそのコンポーネントが変わったらどうなるのか。コミット文書は差分だけを説明することになるのか。そうなると新しいチームメンバーがシステムを理解するには、複数のコミットメッセージを読み、頭の中でマージしなければならない。
.mdファイルや Wiki、Confluence と比較するのは、自転車とガチョウを比べるようなものだ。コンポーネント作成時の考慮事項やトレードオフ、あるいはそうしたものがなかったという事実は、元からの欠陥、その後の進化、ユースケースの変化から生じた欠陥を理解するうえで、しばしば有用だ。
新しいチームメンバーがシステムを理解するには、別途「現在」のドキュメントを維持すればよい。その文書は、実装上のトレードオフや、初稿が時間的制約の中で書かれたという事実まで扱う必要はない。
コミットを squash すると、機能追加の物語としては記録がよくなり、マージは実際のコード変更の不変ログを保存し、rebase はその両方を少しずつ行う。
両方を持てない理由はない。コンピュータをプログラムする人たちなのだから、望むように作れるはずだ。
自分が Git を書き直すなら、コミットを2つの部分に分ける。変更履歴は Git のように Merkle DAG 風の構造で不変のままにし、別の関連データストアにコミットメッセージを保存して、作業の流れを説明する合理的で編集可能なログにする。機能タグを中心にコミットをまとめ、タイポを直し、メッセージを好きなように変えられるようにしつつ、下にある差分ログ、つまり「コードで実際に変わったこと」はそのまま保存すればよい。
git notesで追記することはできる。ただ、メッセージがあれほど長いと、誰かが気づく可能性は低そうだ。同意できない点が1つある。「すべてのコミット、特にこの程度の大きさのコミットに、このレベルの詳細を期待しているわけではない」という箇所だ。
経験上、むしろ小さく無害に見えるもののほうが、相対的に長い説明を必要とすることが多い。
昨日
gh pr listに--limit=999を追加した理由を3段落で書いた。紛らわしいからだ。--jq引数の中にもすでにlimit(があり、PR 全体が無限にあると仮定すると、値が高いほど最終結果は実際には少なくなる。コードコメントも書いたし、おそらく文章を書く時間より、考えて練る時間のほうが長かっただろう。誰かがこの仕事をコードを乱造することだとほのめかしたときには、次の例として覚えておいてほしい。
“This was a non-ascii whitespace character that caused
ArgumentError: invalid byte sequence in US-ASCIIwhen runningbundle exec rake” くらいで十分だ。後で似た問題を検索するためのキーワードがあり、根本原因を含んでいて、長すぎないので人々が読み飛ばす可能性も低い。優れた Git コミットではないと思う。
あれだけ大量のテキストがある割に、最初の行 “Convert template to US-ASCII to fix error” はもっと良くできる。どの空白文字がエラーを引き起こし、そのエラーが何だったのかを数語だけ足せばよい。その説明と差分だけで、必要な文脈は十分だ。
正直、残りはほとんど役に立たない。害はないが、価値も大きくない。作者はこのバグを追跡した道のりを文書化しているが、誰が気にするのかと思う。
記事には、その修正から学んだ人たちが残した複数のコミットを示す検索結果へのリンクもある。
そのコミットメッセージは知識の宝庫だ。
優れたコミットメッセージを見たいなら、Linux カーネルの Git 履歴を見るとよい。そこではこれが標準だ。
1行目は常に変更の影響を受けるサブシステムに言及し、続けて命令形の1行要約を置く。その後で、3つの質問にできるだけ詳しく答える。
例としては「現在のコードは X を行う。テストケース T を実行すると、予期しない挙動 U が観察された。これは理由 R によるものだ。F を行って修正する」のように書ける。
最近のコントリビューターから、私の Git メッセージの書き方、つまり要件が「独特」だと言われた。Linux カーネル出身であることがにじみ出ているようだが、私のコミットメッセージはどれもここにあるもののような形をしている
既存コードに関する内容なら、コメントはコードと一緒にあるべきだ。プロセスに関する内容、たとえば削除されたコードや動作していなかったコードについては、コミットにあるべきだ
最も重要なのは、コミットメッセージは便宜上 issue を参照してもよいが、重要な詳細は必ず再現しなければならない、という点だ。GitHub は一時的なものだが、Git メッセージはそうではない
文脈がたっぷり詰まったコミットメッセージはこちらにある[1]
こうしたことはあまりにもよく起きるため、メンテナーがこの文章を書いた[2]
[1] https://github.com/git/git/commit/d70f554cdf38b0b05cfaa8e8eb...
[2] https://lore.kernel.org/git/xmqqedevo8ps.fsf@gitster.g/