「バグのないソフトウエアは存在しない」――ソフトウエア開発に携わっている人なら,だれしも心の片隅にこうした気持ちを持っているのではないだろうか。

 確かに,ソフトウエア開発の手法が革新的に変わらない限り,実用的な規模のソフトウエアがバグを一つも含まないことはあり得ない。しかし,実用上十分なレベルにまでバグを減らすことは不可能ではないはずだ。実際,ソフトウエアが寿命を迎えるまでに,バグが一度も発現しないなら,バグがないのと実用上は同じである。

 バグの原因はさまざまだが,その多くは開発者の不注意によるものだ。例えばC/C++では,変数resultの値が定数ERRORと等しいかどうかを判別するのに

if(result == ERROR)…

と等号=を二つ重ねて記述する。ところが,これを

if(result = ERROR)…

と書いても文法的には正しいためにコンパイル・エラーにはならない(警告を発するコンパイラはある)。C/C++の習慣に従ってERRORの値を-1などにしていると,このif文の帰結部に記述した処理はresultの値にかかわらず必ず実行されることになる。

大部分は些細なミス,「手抜き」がバグを呼ぶことも

 たかがキー入力ミス一つでと思われるかもしれないが,バグの大部分はこうした些細なミスによるものだ。今から40年前,ハイフンが一つ抜けていただけで米国で打ち上げられたロケットが軌道をはずれ,爆破されたこともある。

 特別なケースを考慮するのを忘れたり,アルゴリズムに不備があったり,というもっと深刻な問題も,プログラマの不注意によるものという点では単なるキー入力ミスと変わらない。市販アプリケーションで最もよく見かけるバグであるメモリー・リーク[用語解説] も,大部分は開発者のうっかりによるものだ。

 これとは別に,「手抜き」によるバグもある。「面倒だ」「時間がない」といった理由でバッファがオーバーフロー[用語解説]したときのエラー処理を省略する,というのがその例だ 。単に必要なサイズのバッファを動的に確保すれば済むものならそうすべきだし,インタフェースの都合でそうせざるを得ないならエラー処理まで仕様として定めておくべきである。

 しかし,現実にはmallocやnewでバッファを確保するのが面倒だ,とか,仕様書に書いてない,といった理由で,安直なコードを記述することになる。こうしたコードがクラッカによるアタックの標的となっているのは周知の通りである。

 開発者の知識や経験不足によるバグ,というのもある。10年ほど前,ローランドのMIDI音源MT-32がIBM互換機のサウンド・デバイスのデファクト・スタンダードの一つであったころ,MT-32の初期型でBGMが正常に演奏されないゲームをいくつかプレイしたことがある。

 MT-32の初期型はMIDIデータの処理が遅いため,大量のデータを一度に送信するとバッファがオーバーフローするのである。MIDI関係者の間ではかなり有名であったこの事実を,有名ゲーム・ベンダーのプログラマや音楽データ作成者が知らなかった,では責められても仕方がないだろう。

 Windowsで印刷する際にプリンタによって出力結果がおかしくなる,というのもよく見かける不具合だ。Windowsでは,画面描画とプリンタ出力で同じAPI関数を利用するが,プリンタ・ドライバによっては一部のAPIをサポートしていないものがある。このため,アプリケーション側でマイナーなAPIを使わないようにするのが鉄則だが,「画面描画とプリンタ出力のコードを共通化できる」といった教科書の記述をうのみにしてコードを書くと一部のプリンタで動作しないことになる。

 こうしたバグは社内テストだけではなかなか見つからない。実行環境を特定できないアプリケーションは,広範囲のベータ・テストが不可欠だ。

バグはテスト工程でつぶすものではない

 バグを減らすにはどうすればいいのか。バグの多くがミスによるものなら,いずれにせよテスト工程が必要になることは明らかだ。人間は必ずミスを犯すものなので,実装工程の段階でバグを無くすことは不可能だからである。

 ただ,ここで主張したいのは,「だからテストをきちんと行おう」ではない。テスト工程よりも前,実装までの段階で,できるだけバグを減らしておくことが大切なのだ。

 理由の一つは,「バグは発見するのが遅くなればなるほど修正コストが高くつく」からである。仕様策定のときに発見されたバグは仕様書を直すだけで済むが,詳細設計やコーディングの段階で発見された場合は作業の一部が無駄になってしまう。テスト工程で不具合が見つかると,原因を探すために膨大なコードと格闘するはめになる。出荷後に発見された場合は,修正版を配布する必要が生じたり,そうでなくても会社の名前に傷が付くことになる。

 加えて,テスト工程で発見したバグをすべて取り除けるとは限らない点にも留意してほしい。実際,多くのソフト・ベンダーは,納期が近い,修正があまりにも大変,などの理由で,優先度の低いバグを修正しないまま製品を出荷している。これは製品に含まれるreadmeファイルやWebサイトなどで「既知のバグ」リストを公開していることからもわかるだろう。

特効薬はないが,ちょっとした工夫でバグは減らせる

 バグを減らすための特効薬は存在しないが,コーディングの際にちょっと工夫することでバグを減らすことは十分可能である例えば,先の==の例なら,

if(ERROR == result)…

のように定数を左辺に記述する習慣を付けることでバグを防ぐことができる。==の代わりに=と書いた場合,定数への代入となってコンパイル・エラーになるからだ。

 日経ソフトウエア2002年8月号(6月24日発売)の特集「バグを出さないプログラミング技法」ではこうした手法をいくつか取り上げているので,興味があれば参照していただきたい。

 一つひとつは些細なものでも,習慣として実行することでバグは確実に減るはずだ。重要なのは,知識として覚えることではなく,日常的に実行することである。

 コードの書き方以外にも,バグを減らすための習慣はいくつかある。手抜きやアルゴリズムのミスにはソース・コードのレビューが有効だ。実際,ブラック・ボックス・テスト[用語解説] を繰り返すよりも,コードを丹念に読むほうがバグの発見率が高いという実験結果もある。

 もちろん,コードを書いた本人以外の開発者がチェックしたほうが効果が高いことは言うまでもない。開発チーム全体でのレビューやXP(エクストリーム・プログラミング,[用語解説] )のペア・プログラミングが無理なら,レビューだけでもペアでやる,というのはどうだろうか。開発者だけでなく,レビューアの名前も記録に残すようにすればおざなりのレビューで済ますことにもならないだろう。

 バグの多くはメソッド単位やクラス単位でのテストで発見できることも忘れてはならない。XPで言う「テスト・ファースト」に従ってテスト・ケースを先に書き,メソッドやクラスを書き上げた段階でこまめにテストするようにする。そうすれば,つまらないバグの多くはアプリケーション全体のテストを行う以前になくなっているはずである。テスト・ケースを書くにはそれなりの時間が必要だが,開発工程全体でみれば結果的に時間の節約になる。

 バグを減らすための手法には,会社が制度として導入しない限り実現が難しいものがあるのも事実。会社側が品質よりも機能や納期を優先する限り,バグは減らないという声もあるだろう。ただ,最後にバグを埋め込む引き金を引くのは開発者である。開発者が「バグは出て当たり前」「バグはテストでつぶすもの」といった意識を持っている間は,いくら会社側で品質管理などの手法を導入したところで大幅な効果は望めない。最後は開発者の意識の問題なのだ。

(山本 哲史=日経ソフトウエア編集)

【2002.07.03追記=おわびと訂正】

エラそうなことを書いておきながら,筆者自身もバグを出してしまいました。
記事掲載時点で,最初の小見出しの直前に
C/C++の習慣に従ってERRORの値を-1などにしていると,このif文の帰結部に記述したエラー処理は決して実行されないことになる。
と記述しましたが,これは,正しくは
C/C++の習慣に従ってERRORの値を-1などにしていると,このif文の帰結部に記述した処理はresultの値にかかわらず必ず実行されることになる。
でした。
たいへん申し訳ありませんでした。おわびして訂正します。