デバッグは、 正しくないプログラムと、 正しく書いたと信じているプログラマとのずれを見つける作業であり、 プログラム作成における最も難しい段階である。
特に、 プログラムを作成してしばらくしてから バグが見つかった場合には、 ソースコード(とドキュメント)のみが拠り所となるので、 デバッグは困難を究める。
そんなわけで、現実のソフトウェア開発では デバッグしやすい(=保守が行ないやすい)コードを書くことが、 「とりあえず動く」コードを書くことよりも優先する。 (動かなくても保守しやすいコードは直すことができる。 とりあえず動くが保守できないコードは担当者が代わったら捨てるしかない。)
なお、不幸にしてプログラマの意図していることが、 そもそも問題解決の手法として誤っていた場合には、 どんなにプログラムが意図通りに記述されていても、正しく動かない。 これは、コーディング前のアルゴリズム設計がいかに重要であるかを示している。
デバッグの流れは以下のようになる。
問題は小さい方が簡単に解ける(ことが多い)。 そこで、手に負えない大きな問題を対処できる小さな問題に分割できれば、 個別の小さな問題を解くことで、最終的に大きな問題も解決できる、ということである。 デバッグでいえば、 バグがどこにあるのかを探すために、ここまでは少なくとも正しく動いている、 この関数は正しく動いている、この時点では既におかしくなっている、、など といったテストを行なうことによって、 バグの在処を 絞り込んでいくということである。
もっとも簡単な方法は、 printf文などを一時的にをプログラムに追加し、 要所要所で変数の値をチェックすることである。 このprintfのように、デバッグのためのコードを デバッグコード(debug code)とかデバッグライト(debug write)と呼ぶ。
UNIX環境でprintf文を使うときには、注意が必要である。 例えば、0による除算がいつ発生したのか調べようとして、 次のように printf文を入れても、何も出力されないうちにcoreダンプしてしまう。
int main (void) { int x, i; for (i=10; i<20; i++){ printf("i=%d ", i); /* debug code */ x=40/(i-15); } }これは unixカーネルによって出力がバッファリングされている (効率よく出力するために、一定量たまるまで画面表示をさぼっている)ためである。 これは、printfで表示する時に最後にきちんと改行させることで(かなり?)回避されるが、 fflushを用いて強制的に画面に出力させるのが正しい方法である。
printf("i=%d ", i); fflush(stdout);
デバッグ時には、そのコードで 「プログラマは何をしたかったのか」を念頭に置かないと、 やりたいこととやっていることの違いを見つけられない。 こういう場面では、適切に書かれたコメントがあると大いに役に立つ。
世の中のコメントは、次の5つに分類されるといわれている。
変数や関数の有効範囲が狭ければ狭いほど、分割統治法は有効に機能する。
例えば、ある変数の値がどこか意図しないところで書き変わってしまう場合、 その変数がプログラム全体のグローバル変数であると、 ほとんどすべての関数が書き換える可能性があるが、 ある関数の局所変数であれば、まずその関数内に間違いがあるとみなせる。
そこで、プログラムをコーディングするときには、 できるだけ相互の依存関係が少なくなるように 関数やデータを分類することが重要となる。
メインとなるデータを決めれば、 それにアクセスする一覧の関数群はある範囲に絞られるはずである。 このような考え方をベースに、 ファイルを分けてコーディングしてゆくと、デバッグ範囲を狭めておくことができる。
よく言われているポイントとしては、例えば次のようなものがある。