C.3. Swarmのデバッグに関するTips

おそらくソフトウェアそのものを記述する以上に、ソフトウェアのデバッグは人間くさい作業です。我々が作るソフトウェアはどれもがバグを持っているでしょう。どうやってバグを診断するか、どうすれば最初からバグの少ない防衛的なコードを書くことができるかという方法論に通じていることは重要です。

C.3.1. バグの発見

バグには2つの種類があります。あなたのプログラムを落とすものと、落とさないものです。プログラムを落とすバグは明白なので好意的だと言えますが、あなたが気づかないバグは危険です。"果たしてこのプログラムは、自分がこう動いていると思う通りに動いているのだろうか?"という問いは、どのプログラマをも捕らえて離さない永遠の疑問です。

gdb. ずば抜けて役に立つバグ発見ツールは優れたデバッガ、つまりプログラムの下で走り、ブレークポイントのセットや変数値の検査などができるシェルです。Unixでおそらく最高のフリーデバッガはGNU ftpサイトにあるgdbです。gdbはとっつきにくく、最初は混乱するかもしれませんが、gdbを学ぶ時間は決して無駄にはなりません。

以下に、最も重要なgdbコマンドを挙げます。 help: オンラインヘルプをブラウズします。 where: どこでクラッシュしたか、スタックトレースを示します。list: 今いるところのソースコードを示します。break: ブレークポイントをセットします。 print: ある表現式の値を表示します。 あなたのプログラムがクラッシュするなら、gdbを使ってプログラムを実行し、スタックトレースを見てください。バグが有るような気はするものの、それがどこに有るか分からないときは、ブレークポイントをセットしてどこで予想からそれていくかを観察してください。

gdbとObjective C. 現時点で、残念ながらgdbはObjective Cを直接サポートしていません。Objective Cプログラムのデバッグを実現するには、しなければならないことがいくつかあります。これらは、Objective Cが構造体(オブジェクト)と見知らぬ関数名(メソッド)の構文を大げさに見せているだけだという認識に基づいています。

defobj: xprint(), xprintid(), xfprint(), xfprintid(), xexec(), xfexec(). Swarmには、デバッグを楽にする関数がいくつか定義されています。オブジェクトのクラスをプリントするxprint(object)や、オブジェクトに指定されたメッセージを呼び出すxexec(object, "message")は特に便利です。call xprint(aHeatbug)とすれば、gdbの実行中にこれらを呼び出すことができます。ただし、メッセージに引数を渡したり戻り値を見ることはできないので注意してください。xfprint(collection)とxfexec(collection, "message")もあります。これらはコレクションの各メンバを印刷あるいは実行するメソッドです。

C.3.2. バグの防止

防衛的なプログラミングをすることは、まとまった数のバグを防ぐのに役立ちます。コードを書いているとき、追加したコードに対してそのテストを試みてください。つまり、その影響が予測し得る小さな変更を行った後に、それらをテストするわけです。おのれの賢さがおのれの首をしめるようなことがないようにしなければなりません。まずは正確にコードを書き、もっと効率的にする必要があれば、その後で修正を加えるようにしてください。間違ってはならない通常の利用法でミスする恐れがあれば、sanity check(健全性のチェック)を挿入してください。

-Wall. 現在Swarmはすべてのコードのコンパイルに"gcc -Wall"を使い、普通は問題にならないことにも警告を発するようにしています。gccはあなたのしていることが容易に誤り得ると判断すれば、そのリーガルコードに警告を生成します。したがって、警告はかならずしもエラーではありません。最初はうっとうしく思うかもしれませんが、関数から値を返すことを忘れたりプロトタイプをインクルードするのを忘れるといった多くの一般的なエラーを捕らえるのに、この警告が役立ちます。-Wallを付与するのは、良い方法です。

nil_method. 本質的にはObjective Cのオブジェクトは構造体へのポインタです。あるオブジェクトをポインタ0x0、つまりObjective C用語用語の"nil"に送ったら何が起こりますか? 残念ながら、gccを含むObjective Cの大部分の実装はnilへのメソッドを効果なしと定義します。以下のコード、

aHeatbug = [Heatbug create: aZone]; 
aHeatbug = 0; // バグだ!
[aHeatbug setIdealTemperature: idealTemp];
      

には何のエラーも生成されません

これはポインタに0をセットして、はからずもポインタを捨てるという一般的なバグです。台無しになったオブジェクトにメッセージを送ろうとしたとき、プログラムがクラッシュしてくれれば素晴らしいのですが、メッセージ送信が静かに失敗し、黙々とプログラムが走り続ければ、バグの発見は困難です。

nilに送られるメッセージに、プログラムをクラッシュさせる方法は2つあります。最も簡単なのは、gdbを使ってnil_methodにブレークポイントを置くことです。そうすれば、nilにメッセージが送られるたびにnil_methodが呼び出されます。もう1つは、libobjcランタイムソースのコピーを作り、nil_methodを好きなように編集することです。src/defobjにあるソースコードには、gcc 2.7.2のランタイムにパッチをあてるobjc.patchファイルがあります。