能書きここ半年ぐらい、僕のまわりでは「Gitに移行しようぜー」的な波が押し寄せてきてる。前職の職場も、今の職場も、大学時代の友達も。僕は、学生時代(3年前ぐらい?)にたまたま偶然Gitに首突っ込んだので、もう既にGitがないと生きていけない体になってしまっているという立場。いうことで、Gitについて思うところを僕なりに一回文章にしてみようと思う。ここに書くのはGit入門ではなく、「Gitの基本コマンドは覚えて使えるけど、どうやったらGitっぽくつかえるか、Gitの恩恵を得られるか掴みきれてない」ぐらいの習熟度の方に参考になるような内容の予定。ここに書いてあることに従えと言いたいわけではないので、違う意見の場合はぜひコメントなど頂ければ幸いですし、Git勉強中の方は1つの意見として参考にしていただければと思う。SVNとの違いSVNなど、一世代(あるいはもっと)前のVCSからGitに移ってくる場合に、SVNとGitの違いという観点からGitを理解しようとすることは多いと思う。そのアプローチ自体は良いとして、1つ問題がある。SVNとGitの違いの話をするときに大抵の場合、まず最初に「中央管理型か分散型か」を挙げられてしまうことだ。それは重要な違いではあるのだが、その違いが重要なケースは稀だ。Linuxプロジェクトのように(開発者の人数という意味で)大規模なプロジェクトの管理をする立場になったら考えることであって、一開発者の立場なら、「へー」ぐらいの理解から始めれば十分だ。(もちろんこれから挙げていくGitの特徴の根本にあるのは「分散型」の恩恵なので、深堀りするときには勉強しよう)というまえがきを置いた上で、僕はSVNとGitとの違いとして、「ブランチの粒度の大きさ」を挙げたい。Gitでは、とにかくブランチを細かくたくさん切るのだ。1行の修正のために1つのブランチを切るなんてことも決して珍しくはない。一例として、今僕は会社でソロ開発してんだけど、7/1から7/25までの1ヶ月弱の間に切ったブランチの数カウントしたら、120だった。SVNでは想像できない数なんじゃないかな。いうわけで、これからブランチを細かく切ると何が嬉しいのかってところを掘り下げて行くことにする。帽子をかぶり分けるためにブランチを切る開発の世界では「帽子をかぶり分ける」という言い回しをよく使う*1のでここでも引用させてもらった。今、機能Aの開発をしているのか、バグBを直しているのか、処理Cのパフォーマンス向上のための改修をしているのか。どの作業をやっているのか明確にした上で、そのことだけを考える。そのためにブランチを切るのである。開発してると、以下の様な場面に出くわすことがあるだろう。機能Aの開発中に、機能Aとは関係ないバグBを見つけた機能Cを作っていたが、機能Dを先に作っておく必要があることに気づいたコメントが気に食わない、タイプミスが気になる、リファクタリングしたい、等々このような場面でそのまま本能の赴くままに手を加えてはいけない。あれもこれも一緒に実装しようとすると、だいたい何か考慮漏れがあって、バグる。そこで、「このブランチではこの作業だけをやる」というルールを自分に言い聞かせて、頑なに守るのだ。checkout)が帽子のかぶり換え。定量的にはわかりにくいかもしれないが、帽子のかぶり分けの効果は、スピードや品質の向上にかなり有効だと思う。ちなみに、「今どの帽子をかぶってるか=今どのブランチにいるか」がすぐわかるようになってると、脳の切り替えには有効だ。手慣れたシェルがあれば、是非、プロンプトに「今いるブランチ名」を表示するように設定しておこう。あとで少し触れるが、マージ作業や、後から変更履歴を確認するときなんかにも、「機能Aのブランチでは必ず機能Aの実装だけが行われている」ことはとても有効だ。"じゃあ、「今のブランチとは関係ないバグB」や「先に作らなきゃいけない機能D」はどうするの?"答えはこう。systemにissue切る(まずは何も考えずにissue切る。これやらないと、忘れるよ。)関係ないバグBは放置。先に作らなきゃいけない機能Dが見つかったら、今のブランチのことはひとまず忘れて、機能D用ブランチを切ろう。できる限り狭い範囲だけを考えればいいように影響範囲を限定しながらソースコードに手を加えるのは、開発の基本。ブランチの切り方も、この考え方にきっちり従っていこう。粒度の話ここまでの話をより深くイメージするには、「機能A」ってのはどれくらいの粒度なのよってのがあると良いと思うので、ここで触れておこう。僕は1つのfeatureブランチは、だいたい以下の様な大きさに収まるように、という方針で切っている。(もちろん状況によるので必ずしも全てのブランチがそうなっているということはないが。)ブランチ単位のdiffをとったとき、出てくる各diffが何を意図して行われた変更か、すぐに想像できる程度ソースレビューなんかを実施する文化で開発したことのある方だと想像しやすいと思うが、ちょっとdiffの量が増えると、すぐ「必死にdiffを読み込まないと意図が把握できない」という状況になってしまう*2。ならない程細かくブランチを切るのだ。そこまで細かくするか。繰り返しになるが、それだけ「考える範囲を限定する」ことによってスピードと品質の高い開発をしようというのが一点。ひとつは、「マージのため」という視点だ。VCSの利用において、一番事故につながりやすい作業が「マージ」だ。できるだけマージはしたくないという意図はわかるが、Gitの考え方は逆方向で、「マージの回数は増えても、安全なマージをする」だ。単純に、diffが少なければ競合しにくい。マージってのはブランチ単位でするものだから、ブランチが小さければ小さいほど競合しにくいという至極単純な理屈。競合は起きるものであり、いかに競合を正しく解決するかも考えなければいけない。競合解決するために必要なものは「コード変更の意図を正しく把握すること」だと僕は思っている。ブランチの規模は「diffを見て意図をすぐ読み取れる」程度にしておくと、競合した時にお互いの変更をどのように組み合わせるのが正しいか把握しやすい。この辺りは文章では伝わりにくいかもしれないので、是非手を動かして実感して欲しい。CIとの相性あともう一つ、ブランチを細かく切るということは、CI(継続的インテグレーション)との相性が良いということにも言及しておきたい。ビルドを回すには、最新のコードが「動くもの」になっている必要があるが、複数featureが混ざった長いブランチで開発していると、「動く状態」のタイミングが訪れにくい。「機能Aの実装は終わったけど機能Bが中途半端に実装開始している状態」コミットとかね。なると、結局全機能実装し終わるまでテストを回すことができなくなって、それって結局CIでもなんでもないということになる。それに対して、あくまで最小単位の機能でブランチを切り、その機能だけが新たに動く状態になった時点でマージするというのがGitのスタイルになる。マージ先になるブランチには、機能が出来上がるたびに、プロダクトが動く状態でマージされていくので、このブランチはCIツールに監視させるブランチにピッタリだ。modelをイメージして書いてます。これで言う、「develop」ブランチは、CIのためにあると僕は思っている。)なんかGitの使い方というより、ブランチの切り方の話してない?仰るとおり。ブランチの切り方が大事だと思っているのは確か。ツールの機能としてはSVNでもこのブランチの切り方を実践することはできるだろう。このブランチの切り方を実践するにはGitじゃないとダメなんだ、これが。この開発スタイルになってくると、ものすごい頻度でブランチ切るし、diff見るし、マージすることになる。SVNだとそれらのコマンドを打つたびにリポジトリサーバと通信が走り、待たされる。Gitならこれらのコマンドはすべてローカルリポジトリで完結するから、一瞬で結果が表示される。分散リポジトリ型のVCSが、Gitがイイ。一度Gitに慣れてしまってから「もうSVNには戻れない」って言う人は、そのサーバと通信してる数秒×何千回の時間浪費に戻れないという面が大きいのだと思う。まとめというわけで何が言いたかったかというと、僕は別に「Gitのこの機能が良い!」という風には全然考えてない。「CVSからSVNに移行したときのように、SVNからGitに移行した時も打つコマンドが変わるだけ」というGitの使い方では意味が無いのだ。それは、「ちっちゃいブランチを切る」ことから始まる新しい開発スタイル*3だと僕は思っている。TDDでは「テストを書いてコンパイルを通す」「テストをグリーンにする」「リファクタリング」のステップがあり、明確に作業を分離する。「コンパイルを通す」のステップで実装(テストを通す)を考えてはいけない。このように今どの作業をしているのか明確にし、その作業のことだけを考えるというスタイルを「帽子をかぶり分ける」と表現する。「そして形式だけのソースレビューが行われて、レビューの効果が出るわけもなく、バグる」までが予定調和。ゆーてもGit自体そこまで新しいというわけではないので「新しい開発スタイル」は言いすぎかもしれないが、少なくともSVNベースとは違う開発スタイルになる。