バグとどうつきあっていくか

ソフトウエア開発においてバグは避けては通れないものです。バグが無いことは証明できないので、いかにしてうまくつきあっていくかというのが重要になってきます。以下僕がこうやればいいのではというのを書いてみます。まあほとんどがブログやら書籍やらの受け売りで僕が実践できているのはごく一部だと思いますが、ともかく書いてみます。バグの定義は顧客と決めた仕様を満足していないものとし、バグはTracのようなバグ管理システムで管理するものとします。またソースはSubversionのような構成管理ツールを使っているとします。ま、当たり前ですよね。

1.単体テスト工程までにできるだけバグが無い状態にしておく
当たり前のことですがちょっとつっこんで考えたいと思います。まずコーディングの段階ですがあわてて書かないほうがいいでしょう。進捗会議のときに報告する材料がほしいのはわかりますが、落ち着いてまずどんなふうに実装していくかを頭のなかでイメージするといいと思います。そのイメージをメモ書きするのもいいと思います。処理を1つ1つ箇条書きにしていくともっといいかもしれません。この箇条書きされたものがメソッドのJavaDocコメントになる感じで。またこの際おおまかなデータ構造に関して考えた方がいいでしょう。データ構造の設計に失敗すると後で大変です。ここまでのプロセスをまったくコード書かずに進めるのが難しい場合は少し実験的に書いてみるのも手です。コーディングはあわてて着手してもよくないし、あまりにも頭の中でこねくり回しすぎて実際のコーディングからのフィードバックを得ないのもよくないです。この辺難しいですね。ともあれ疎通確認を随時やりつつコーディングをしていきます。JavaならSystem.out.printlnじゃなくてロガーを使いましょう。あと疎通確認をどれだけいろんなパターンでやれるかも重要ですね。ロールバックの確認とかデバッガの力借りないと無理でしょう。蛇足ですが僕はデバッグ用に書いたと思われるSystem.out.printlnのなかでNullPointerExceptionが発生しているのを発見して椅子からずり落ちそうになったことがあります。。。次にやることは単体テストチェックリストの作成です。テストを自動化するにせよしないにせよチェックリストは作成したほうがいいでしょう。僕はチェックリスト作成中に処理の考慮漏れに気づいたことがあります。またチェックリストをレビューしてもらうことにより仕様漏れを防ぐこともできます。

2.結合テスト以降でバグ発見
結合テスト、総合テスト、リリース後の顧客からの報告、なんであれバグを見つけたとしましょう。まずはバグの再現手順を確認します。なるべく最小の手順で再現できることをめざします。それができたらバグの登録です。タイトル(サマリ)にはユーザ視点で書きましょう。「○○画面で××ボタンを押したらシステムエラー発生」のような感じで。「○○クラスでNullPointerException発生」のようにシステム側の視点から書いてしまうと後で見た人が自分が新たに発見したバグがこのバグと同件なのかわかりません。次に詳細です。ここに再現手順を箇条書きで書いていきます。「○○ユーザでログイン」、「××で検索」みたいな感じで。期待される結果、実際に観察された結果も書いていきます。また必要に応じてログや画面キャプチャも添付します。発生した環境、バージョンはそれ専用に書く欄があればそちらに書きます。無い場合もどこかに書きましょう。ま、くわしくは「実践バグ管理」とかやさしいバグトラッキング - The Joel on Software Translation Projectかな。

3.バグの修正
さてバグ修正です。ログ出力がちゃんとしてるならそれでかなりわかると思います。またスタックトレースが出ているようなバグなら比較的楽でしょう。ログやスタックトレースを見て原因箇所をしぼりこめたならデバッガの出番でしょう。ブレークポイントをおいて変数の内容を見ていきます。JavaでいうとArrayIndexOutOfBoundsException, StringIndexOutOfBoundsExceptionのような配列の境界に関する単純なコーディングミスならデバッガですぐわかります(これぐらいのバグは単体テストでつぶしとけという意見はあるが)。こういうミクロな視点で見たい場合のデバッガの力は圧倒的です。一方JavaでいうNullPointerExceptionのようにnullに関するものでしかもequals(null)のように単純なコーディングミス(ひどいミスだw)でない場合はやっかいです。NullPointerExceptionなんだからnullになっているのがおかしいわけですが、じゃあいったいいつnullになったのかが問題なわけです。つまり一見して間違ってはいない場合や原因が例外発生箇所(あるいは不正表示のように問題が観察された箇所)から遠くにある場合は問題解決が難しいです。こういう場合はデバッガよりもログが頼りになってきたりするわけです。あるかどうかわからないけどSerializableじゃないオブジェクトがセッションにバインドされていてそれが原因でセッションレプリケーションがうまくいかずNullPointerExceptionが発生なんて場合は(こんなケース再現できないだろうけど)デバッガじゃなくてログが頼りになってくるでしょう。ログを見て仕様を確認し落ち着いて全体像を見る。マクロな観点で考えることが重要になってくるわけです。

4.バグ修正が完了したのでSVNコミット
ともあれバグ修正が完了しました。SVNコミットするまえにいくつかチェックするポイントがあります。
・差分の確認
まず差分を確認して不要な空白など除去しましょう。何が言いたいかというとバグに関係した差分以外のものはのぞきましょうということです。あとで差分を見る人がわかりやすいようにする必要があります。同じ理由でソースのフォーマットをやってしまうとわかりにくくなるのでそれは別コミットにしましょう。
・1つのバグに対して1つのコミット
基本的に1つのバグに対して1つのコミットが原則です。2つ以上のバグに対してまとめて1回でコミットされるとどれがどのバグ修正なのかわかりません。また1ファイルごとにコミットする人を見かけますがこれもやめましょう。関連するものはまとめて1回のコミットです。
・コミットコメントにバグ番号と修正内容を入れる
Tracのpost-hook-scriptを使っていればリビジョンとバグID(Ticket)のリンクが自動的につきますのでこれを活用しない手はありません。コミットコメントにはバグ番号だけで修正内容はいらないという意見もありますが、個人的にはあとで履歴を見る際にわかりやすいのであったほうがいいと思います。ソースにバグIDをコメントとして入れるという文化もあってこれはメリットとデメリットあります。

メリット
あとでソースを読む際にコメントとして書かれたバグIDがヒントになることがある。
デメリット
コメントが多くなるとソースが汚くなる。バグIDをコメントに書くのが面倒。

まあコードコンプリート下巻の33章p399に以下のようにあるのでコメントには入れないほうがいいんじゃないでしょうか。

このようなコメントは、バージョン管理ソフトウエアで処理する方がよい。コメントは、今コードが動いている理由を説明するものであり、過去にコードが動かなかった理由を説明するものではない。

5.バグ修正の確認
バグを登録した人が修正を確認しTicketをクローズします。

ま、こんな感じでしょうか。

参考にしたのはこのあたりです。

以上。