「読む側」からのコミットの絵文字👍ルールの話

はじめに

Gitのコミットをわかりやすくする為の手段の一つとして、コミットの頭にその種類を示す絵文字等の記号をつけるというルールがあります。

弊社 iOSチームではこの絵文字ルールを導入してから5年程が経過していますが、単に導入するだけでもメリットがあると思う一方で、細かい部分で特に意識してもらう事でよりわかりやすくなるようなポイントもあり、それはこのルールの導入意図や背景を含めた形で伝えた方がより理解がされやすいと思った為、改めてその辺りについて簡単にまとめようと思います。

絵文字ルールの方法自体は先人のわかりやすい記事がたくさんありますので、そちらを参考にして頂けると良いと思います。

モチベーション

コードレビューをするのって大変だし難しいですよね?(少なくとも僕はそうなのですが)

チームで開発する上で、多分?誰もが意識しているであろう「コミットをなるべく細かく行う」という指標があります。
これはチーム開発だからこそ特に求められるもので、一人で開発するのならばそんなに強くは意識しないですし、必然性も無いと思います。
例えば1プルリクエスト1コミットで200行変更とかでも、一人の開発ならばまあ特に問題にはならないでしょう。(もちろん細かい方が、revertとか cherry-pick出来るとかのメリットを享受出来ますが)

ここで僕が言いたいのは、ことこのルールをチームメンバーに求めるというのは、コードをレビューする側「読む側」にとってそうして欲しいからだという事です。

そして、それを端的に実現しているルールとして、コミットの絵文字👍付与ルールがあります。

背景

当初、例えばメンバーのコミットで、「〜の機能変更」と書かれた数十行にわたる変更がありました。
しかし実際に変更があった箇所を注意深く探していくと、結局の所特定の1行だけがその挙動変更を実現するものであり、それ以外はリネームや並び替え等、「〜の機能変更」とは直接関係ないものだった・・・
というような事が割と日常的にありました。

この状況を改善すべく、各コミットの適切な分割単位を意識する事を出来るようにする為に、絵文字ルールが導入されました。

どの絵文字をつけるのが適切?

先人の絵文字ルール系の記事を見て貰えば絵文字の種類は10種程度はありますし、どんなコミットにもどれかが適切に当てはめられそうな気がします。

しかし実際に運用してみると、割とどの絵文字を割り当てるべきか迷う事も多いです。
実際メンバーによって、特定の種類の変更に対する絵文字のつけ方が異なる例も多くあります。

ただ、それがことさら問題だと言いたいわけではありません。
目的は「コミットが適切に分けられて」いる事によって「読みやすい」変更になっている事であり、実際につけられた絵文字が適切なのかというのは二の次なのです。(もちろん適切である方がより良いですが、これの正誤で議論するのは行き過ぎると不毛になりかねない部分です)

とはいえ、数ある絵文字の中でも大雑把に性質の異なる区分があり、そこの違いはなるべく厳密に分けられていて欲しい、という事をここでは説明したいと思います。

「読みやすい」という事

しかし、実際のところ「コミットが適切に分けられて」いて「読みやすい」というのはどういう事なのでしょう。
極端な事を言えば、読む人が個別のコミットを見ないで、プルリクエスト丸ごとの差分を見るレビュースタイルならば、コミットを細かく分けているかどうかなんて全くどうでもいい事なわけです。

つまり、「読む側」の状況を知らない限りは、「書く側」にとって「読みやすい」という事はわからないし、細かく分ける意義もわからないという事でもあります。

「僕」にとっての「読みやすい」

ここまで「読む側」が云々と書いてきましたが、要するにここで書いている事は「僕」にとっての「読みやすい」であり、詰まるところ「俺の読みやすいようにコミットを書けコノヤロウ!」という暴論でしかない内容でもある事を断っておきたいです。

実際に僕の基本的なレビュースタイルがどういうものかというと

  • 1コミットずつ全部の変更を見る
  • 書き手の変更意図を把握し、それに沿った変更であるかを見る

という形です。
これを前提に、この見方をする上で絵文字ルールで特にメリットが大きい点がどういう所にあるのか、どうしてこれを厳密に分けて欲しいのか、という事について伝えていきたいと思います。

絵文字👍ルール

絵文字ルールでは沢山の絵文字が定義されていますが、その分類は大きく3種類に分ける事が出来ます。

  • 👍: アプリ挙動が変わる(機能追加, 変更)
  • 🎨: アプリ挙動変更無いが、コード上のロジックに変更がある(リファクタリング)
  • ✏️: コード上のロジックに変更が無い(並び替え, リネーム)

その上で、これらの変更がなるべく分けられている事で、読みやすいコミットとする事が出来ます。

他にもバグ修正、新規機能追加、削除、テスト追加等、いろいろな絵文字定義がありますが、これらは大きな意味では上の3つのいずれかに分類する事が出来ると思いますし、それぞれ変更としては割と特徴が強いので、絵文字の設定が適切でなくてもまあ見ればわかる、というようなものが多いと思っている為、今回はそれらの区別は割愛します。

レビューの視点と難易度

例えば、「背景」に書いたような例ならば、あまり深く考えなければ「アプリの挙動が変わっている」という事実を元に

  • 👍: ~の機能変更

という1コミットで表現する事も出来てしまいます。
しかし、実際は以下のように変更を分けて欲しいです。

  • 👍: ~の機能変更 (差分は1行のコミット)
  • ✏️: hogehogeを hugahuga にリネーム
  • ✏️: ~の処理の記述位置を移動

正直このくらいの例では、「そりゃ分けた方がいいじゃろ」とみんな思う筈だとは思いつつ・・・
ここで言いたい事として、これら3種類の変更は、それぞれレビューの注視するべき視点や難易度が異なってくるという事です。

レビュー難易度の低い変更

例えば、「✏️: ~の処理の記述位置を移動」 について、この変更について1行ずつ、書かれたロジックについて理解しながら読むでしょうか?
多くの人はそうではないと思います。
移動したコードなのだから、その「移動の仕方」に挙動が変わるような点がなければ、挙動は以前と変わらない筈です。

もちろん、書かれたコードを都度見直すというのは、より良いコードにする事に繋がる可能性がある側面もあります。
そこにもしかしたらバグが元々含まれているかもしれません。
しかし、そのコードは過去に既にレビューを通っている筈であり、これまで動いてきたコードでもあります。
これを「記述位置が移動」した事を理由に再びじっくりレビューするというのは筋が通らないし、非効率的です。

つまり、「✏️: ~の処理の記述位置を移動」 はレビューの難易度としては低く、読み飛ばして良い変更だという事になります。

同じく、「✏️: hogehogeを hugahuga にリネーム」についても、「名前の変更」の部分だけを注視し、それ以外の変更が入っていない事が確認できれば、ほぼ読み飛ばす事が出来るでしょう。 これも同様に難易度としては低い変更です。

レビュー難易度の高い変更

一方で、「👍: ~の機能変更」についてはどうでしょうか。
これは挙動が変わっている変更なので、以下のような視点になります

  • 意図した通りの挙動変更が実現出来ているか
  • 意図した以外の挙動変更が起きていないか(この変更による副作用で他の挙動が壊れていないか)

この判定はなかなか難易度の高いものになります。
バグが仕込まれる可能性も高いでしょう。
この時に変更行数が1行であるならば、その実現方法が妥当で、バグがなさそうか、といった判断はそれほど難しくは無いでしょう。

しかし仮にリネームや並び替えのコミットが入っていて、挙動が変わっている1行の特定が困難であったら・・・?
レビュアーの視点からすれば、挙動が変わっているのが1行とは最初の時点ではわかっていないのだから、それを判断する事の難易度は高くなり、余計な時間と労力を使う事になるでしょう。

こうして欲しい

上記のように、変更の種類によって難易度が異なってくるので、なるべく差分としては難易度の低いものを多く、高いものを少なくして欲しいという事になります。
つまり、変更量としては

  • 「👍 < ✏️」 となるように、なるべく多くの変更を ✏️ に寄せていって欲しい

という事です。 

挙動変更の有無(👍 or 🎨)

難易度の高低について書いてきましたが、挙動変更の有無はこれにどう影響するでしょう。
実際のところ、これは「その変更内容による」という感じで、一概にどちらの方が難しいとは言えなさそうです。

しかし、挙動変更の有無それ自体は、レビューする時の視点を決める時の手がかりとしては大きく、これが区別されているかどうかはレビューの難易度に影響を与えます。

🎨: アプリ挙動変更無いが、コード上のロジックに変更がある

例えば、「🎨: アプリ挙動変更無いが、コード上のロジックに変更がある」の変更を見る場合、この絵文字によって、書く側が「挙動を変えていない」つもりで変更をしているという事がわかります。

つまり、レビューをする上で「挙動が変わっているかもしれない」ような変更があれば、それは書く側が意図していない変更である可能性が高いという事になります。
こういった変更はバグやそれに近い挙動の悪化に繋がっている可能性が高く、注意深く見るべきポイントとなります。

従って、「挙動が変わっていない事」を確認するというのが主なレビューの視点となります。

👍: アプリ挙動が変わる

一方で、「👍: アプリ挙動が変わる」の変更を見る場合、この変更は挙動が変わっているのですから、その「挙動変更を実現している部分」を注視する必要があります。
上でも書いたように、単にコードそのものの変更が問題ない事だけでなく、その変更によって実現されている挙動が想定通りであるかも考えなければなりません。

従って、レビューの視点としては「アプリの挙動の変更部分」に集中したいという事になります。

この時、「🎨: アプリの挙動が変わらない変更」が同じコミットに混ざっている場合というのはある程度仕方ない状況として起き得る事ですが、👍と🎨で見る時に必要な「視点」は異なってくる為、出来る限りこれが分けられている事でレビューの難易度は低くなり、精度をあげる事が出来るでしょう。

こうして欲しい

こういった点から、それぞれの難易度を下げる為に意識して欲しい分け方としては、

  • 「👍 < 🎨」 となるように、なるべく多くの変更を 🎨 に寄せていって欲しい

という事です。 

上の 「👍 < ✏️」  とも合わせると

  • 「👍: アプリ挙動が変わる < 🎨: アプリ挙動変わらない < ✏️: コード上のロジック変更無い」  という形で、左側の変更をなるべく少なく、右側の変更をなるべく多く、として欲しい

という事になります。

新規実装とリファクタリング

上で、👍か🎨かのレビューの難易度は「場合による」と説明しました。
これは特に、そのプルリクエストの種類によって違いが出てくる部分なので、それについて書きます。

新規実装

新規実装は、これまでに無かった挙動を追加するわけですから、必然的に「 👍: アプリ挙動が変わる」の変更が多くなります。
ここでさらに新しいコードが、純粋に新しい挙動を追加するものなのか、既存の挙動を変えながらの新しい挙動なのか、によってレビュー難易度は変わってくる部分ではありますが、この点の違いは実際のところ差分を見ればどちらの変更かはわかりやすい部分で、適切にコミットが分けられていればわかる部分です。

ここでは、👍 で表現されるコミットの多くは新しい部分で、既存への影響はあまりなく変更されている部分が多い筈であり、その手の変更では過去の実装に対する副作用等の心配はあまりありません。
なので、もちろん内容によりますが、👍 で表現されるコミットは難易度の低い変更である事が多くなります。

一方で、🎨で表現されるコミットは既存のロジックを今回の実装の為に書き換えている部分であり、なぜその変更が必要になったのかという背景も含めて、ある程度注意深く見るべき内容になる事が多くなります。

リファクタリング

リファクタリングは、基本的にはその作業全般でアプリの挙動は変わらない前提の作業です。
なので、その中で「 👍: アプリ挙動が変わる」の変更は、予期しない変更が含まれてしまう可能性のある注意深く見るべき変更となります。

また必然的に 「🎨: アプリ挙動変更無い」の変更が多くなる・・・
と言いたい所ですが、実際のところはその内容や手順によってこれは大きく変わってきてしまう部分でもあります。
というのも、例えばとある機能の設計を丸ごと差し替える、というような変更である場合、その変更が1コミットで収まるという事はほぼ無いでしょうし、むしろ収まっているような差分では巨大なコミットになり過ぎているだろうと予想されます。

しかし、そうでないリファクタリングも多くあり、手順を工夫する事でなるべく 「 👍: アプリ挙動が変わる」の変更を少なくして、「🎨: アプリ挙動変更無い」の変更を多くする事が出来ないかを考えてみて欲しいです。
これは実際に意識してやってみると、余計な手間が増えたり、非効率的と感じられる事が多く出てくると思います。
しかしレビュー難易度は明らかに下がるでしょうから、レビューまで含めた効率はむしろ上がると考える事が出来ます。
また書いている本人としても、作業手順としては安全側に倒れたものになるメリットもあります。

ただこの辺りの解決策としては別の手段として、テストを先に書いておいて変更するという方法があり、むしろその方が挙動の確かな証明が出来るので理想的です。
この場合は、あまりコミット手順等に拘らなくても良いでしょう。

余談ですが、僕はコミット説明文を書く事は、数学の証明問題を回答するような事だと思っています。
要するに結果を書くだけというのは不十分であり、何かしらの手順でその変更が妥当である事を証明する必要があるという事です。

まとめ

いろいろ書きましたが、伝えたいこととしては大した事はなく

  • 「👍: アプリ挙動が変わる < 🎨: アプリ挙動変わらない < ✏️: コード上のロジック変更無い」  という形で、左側の変更をなるべく少なく、右側の変更をなるべく多く、として欲しい

という点に集約されます。

その上で、そのモチベーションが伝わらず「見る側」(と言いつつ「僕」ですが)が何を分けて欲しいと思っているのか、何故そうして欲しいのがが分からないと、特に区別して欲しい部分や、そもそも区別するモチベーションも出てこなくて、絵文字ルール自体が形骸化してしまうので、その点について伝えようと思ったという次第でした。