コード開発プロジェクトにおけるソース管理システムの正しい利用法
ソース管理システムを使用する本質的な目的は、プログラマに余分な負担を掛けることなくプログラミング作業に集中させることである。そして開発対象のソフトウェアに追加した変更が思っていた程の効果を発揮しなかったり、あるいは追加そのものが間違っていたという場合でも、ソース管理システムを使用していれば最後にチェックインしたバージョンへと即座に復帰させることができる。またコードベース開発を複数のプログラマが並行して進めていくケースにおいても、ソース管理システムを介することで各自の開発成果をスムースに統合できるようになる。実際、複数の人間でソフトウェア開発にあたる場合にソース管理システムが不可欠であるとされているのは、こうした2つの特長を備えているからに他ならない。
現行のソース管理システムは、オープンソースおよびプロプライエタリの双方の形態で提供されている。自分がどれを使用するべきかの判断には様々な要因が関与するものだが、実際には選択の余地無く特定システムの使用が強制されるというケースも多いだろう。もっとも最新のシステムであれば、些末的な機能を除いた大部分の基本機能における相互運用性が確保されているので、今現在どのソース管理システムを使用しているかは特に問題とならないはずだ。いずれにせよ使用中のソース管理システムに付随するメリットよりデメリットの方が高いと感じたなら、その置き換えを検討するべきだろう。そうした候補となるのはCVS、darcs、Subversionであろうが、これらの詳細については個々のWebページを参照して頂きたい。このうちSubversionについては本丸ごと1冊分に匹敵する本格的なマニュアルが用意されており、これは基本的にはSubversionを対象とした解説書だが、他のソース管理システムにも共通する有用な内容が含まれている。
多くのプログラマがしでかす致命的なミスの1つに、こまめなチェックインを怠るというものがある。確かに手を加えている途中のコードとは不完全な状態に置かれているのであり、その他様々な理由が相まって、そうした状態でチェックインするのは正直気が進まないといったところだろう。実際、メインの開発エリアにてコードのチェックインを避けるというのも1つの判断ではあるが、だからといってチェックインをいっさい行わないという方針が正当化される訳ではない。これに付随する最大の問題は、次の段階の変更をコードに加える際において過去に行った変更をすべて失う事態が懸念されることである。つまりチェックインを怠れば怠るほど、1つのミスですべてを御破算にしてしまう危険性が高まることになるのだ。
チェックインを行う際には多数施した変更内容の完全性が気になるものだが、そうした懸念に煩わされなくするにはブランチ(branche)という概念を理解しておく必要がある。ここで言うブランチとは、複数の開発目的ごとに異なるソース管理リポジトリを運用することにほぼ相当する。ただし実際に複数のリポジトリを運用するよりもブランチを用いる方が優れているのは、個々のブランチにて各自が施した変更にはチームメンバの誰もがアクセスできるのと同時に、開発用のメインライン(main line)にて他のメンバが施した変更箇所にも直接アクセスできる点である。その後の変更作業が順調に進み“最終的”にチェックインしても問題ないと判断できるレベルに到達できたら、自分のブランチをメインラインにマージ(merge)し直すことになる。こうした用途に用いられる個々のブランチは通常、個人ブランチ(personal branch:チェックインする人間が自分だけの場合)ないし機能ブランチ(feature branch:複数の人間が共同で作業に当たる場合)と呼ばれている。
darcsというコード管理システムでは、個々のチェックアウトごとに独自のブランチとする仕様になっている。この場合各開発者は、各自が担当するコードをチェックアウトし、追加した変更内容は適切な間隔を空けてパッチとして登録するということになる。
$ darcs get http://example.com/trunk your-local-branch
Subversionの場合、ブランチのマージに関する構文は分かりやすいが、-r
パラメータで指定する値は多くの場合ユーザが手作業で管理しておく必要がある。
$ svn merge svn://example.com/repo/branches/branch svn://example.com/repo/trunk -r5:9
ブランチおよびマージは、ソース管理システムを運用していく上で最も取り扱いの難しい作業となることが多い反面、最も有用な機能でもある。いずれにせよ適切なマージの実行法をマスターしておけば、ソース管理システムを最大限有効に活用できるはずである。
ソース管理システムの運用でよく見られるその他のミスは、複数の変更内容を1つのチェックインにまとめてしまうことである。チェックインの頻度を高くしておくとこの問題は回避できるが、仮に2つのコードがそれぞれ異なるバグを修正しているといった場合、わずかコード2行分の変更に対応するチェックインであっても、それは複数の変更内容に対応することになる。つまり、論理上1つと見なせる変更を施すごとに1つのチェックインを実行していくべきなのだ。具体的な目安としてはバグを1つ修正するごとにチェックインも1回実行しておけばいい(ただし影響が広範囲に及んでいるバグについてはその修正完了までに複数のチェックインをすることもある)。同様に機能の追加についても、それが小規模なものであれば1つの機能を付け加えるごとにチェックインを1つ設けるようにしておく。もっとも中規模ないし大規模な機能追加をする場合は、ブランチ内のいくつかの論理ポイントにて複数のチェックインを設定する必要が生じることもあるだろうが、メインの開発エリアへのマージは機能が完成してから1度にまとめて実施するようにするべきである。
論理的な変更内容とチェックインを1対1に対応させておく最大の理由は、バグ発生時の対処に要する時間を短縮させるためである。つまり1つの変更ごとに1つのチェックインが設けられていると、品質保証(Quality Assurance)担当者はバグの混入した可能性の高い箇所を早期に特定でき、チェックインのログを確認する際にも、個々のエントリごとに何が行われていたかを簡単に把握できるようになる。またメインラインに反映する変更を個々のチェックインごとに1つの完結した内容としておくと、生じてしまったバグを後から修正できなかった場合でも、問題を引き起こした変更以降のチェックインを取り消すという措置が比較的簡単に行えるはずである。そうではなく1つのチェックインが論理的に2つの変更に対応していると、そのチェックインの取り消しは必要以上の変更を取り消すことになるので、無関係な機能を消し去ったり他のバグフィックスを無効化してしまうことが懸念される。そしてこうした対応関係は、論理的な変更内容が2つである場合より1.5個分といった中途半端な対応をしている方が始末が悪いものである。
darcsの場合、どの変更をチェックインするかをユーザが明示的に指定する機能が設けられており、同一ファイルで複数の変更が行われている場合であってもこの機能は適用できる。1つの変更作業が終了して次の作業に取りかかる前に行うべきチェックインを忘れがちなユーザにとって、この機能は特に役立つはずだ。
$ darcs record hunk ./README 7 + +Baz. Shall I record this change? (1/?) [ynWsfqadjkc], or ? for help: y hunk ./TODO 1 +foo! Shall I record this change? (2/?) [ynWsfqadjkc], or ? for help: n hunk ./TODO 23 +bar? Shall I record this change? (3/?) [ynWsfqadjkc], or ? for help: y What is the patch name? Bazbar Do you want to add a long comment? [yn]n Finished recording patch 'Bazbar'
1つの変更内容ごとに1つのチェックインという原則を遵守することで開発者自身が享受できるメリットとしては、各自の担当するソースツリーにて過剰な数の変更ファイルを保持しなくて済むという点が挙げられる。私自身の経験上ビルドが破損するトップの理由は、行っておくべき変更内容のチェックインを開発者が実行し忘れていたか、あるいは逆に行うべきではない内容をチェックインしてしまったというものである。1つの変更内容ごとに1つのチェックインという原則を守るようにしておくと、ビルドの破損を気にすることなく、各自のソースツリーにて変更を施したすべてのファイルをチェックインすることができるようになる。
その他によくやるミスとしては、個々のリリースとソース管理システムとの対応付けという、管理的な側面から発生するものがある。一般ユーザの手元に配布可能な完成度に達したと判断された段階で特定のバージョンを付けた“リリース”を実施するというのは、どのようなソフトウェア開発プロジェクトでも行われている方式であろう。同様に開発リリースと安定リリースという使い分けも大部分のプロジェクトで用いられているが、これらはそれぞれ新機能を実装するための開発用バージョンと、そこにバグフィックスを施しただけのバージョンという意味で使用されている。もっともこうしたリリースの扱いに不慣れな人間にとって、リリース公開とは、単にソースリポジトリ上のコードがある程度完成したと判断した段階で行うtarボールの作成作業にすぎないのではなかろうか。しかしながらソース管理システムを利用すれば、よりシステマチックな方式にてリリースを進めていけるはずであり、実際そうするべきなのである。
望ましい管理方式としては、個々のリリースごとにtarボールを無頓着に作成していくのではなく、ソース管理システム上にてタグ(tag)を用いたリリースの区分を行うべきである。ここでのタグとは、その名称からも推測できるように短めのテキストであり、このケースでは各自のリポジトリにおける時系列を区別するためのテキストを付けておく。具体的にはtarボールを作成してもよい完成度に達したら、該当するコードにリリース識別用のタグを付けてからtarボールを作成するようにしておけばいい。これにより特定のリリースを再現したり(誤って保管用のtarボールを削除してしまった場合など)、2つのリリース間に行われたチェックインを精密に管理することができるようになる。
タグ付けは実行する上での負担も小さく、CVSを利用する場合であっても、ローカルで行うチェックアウトと作成すべきリリースとが対応していれば次のようなコマンドで簡単にタグ付けが行える。
$ cvs tag release_1_0_0
ソース管理システムを利用する上で、タグ付けそのものは特に難しい作業ではない。より困難なのは、安定リリースに施す変更をバグフィックスのみに限定しておくことである。そのためのアプローチは、ブランチを適切に使い分けることだ。まず安定版(例えば1.0など)として公開すべきリリースの準備が整えば、新規のブランチを設けると同時に当該ブランチへのタグ付けを直ちに行うようにする。その後行うメインラインへの機能追加時にも、安定ブランチについて施す変更はバグフィックスのみに止めるようにしつつ、このブランチ上での適切なポイントにてタグ付け方式のリリース(1.0.1や1.0.2など)を継続していく。そしてメインライン側で開発していた新規機能を最終的に安定させるべき段階に到達したら、再び新たなブランチ(ここの流れで言うと1.1など)を設けた上で安定化のプロセスを再度進めていくようにする。こうしたアプローチを採用することで、ソフトウェアの開発過程で発生する雑多なバージョンを、各自のプロジェクトやビジネスモデルで課せられる要件に則した形でサポートできるはずだ。