達人になるために知っておきたいテクニック

デバッグの達人になろう!


礒山賢司 ISOYAMA, Kenji



“バグ”。んー、いやーな響きです。
 本誌をご覧の読者の方々も、一度はこの“得体の知れないもの”に、悩まされたことがあることでしょう。  Visual Basic(以下VB)に限らず、プログラミング言語を利用し、アプリケーションを開発する過程では、この“厄介物”がつきものです。  そこで、本稿では、このバグをやっつける、デバッグに焦点を当てて、筆者の経験も交えながら、「どう対処していくか?」をVBマガジン流に「お気軽」に改めて考えてみたいと思います。

“デバッグ”って?

 まず、簡単にデバッグについて、ご説明しましょう。
 われわれプログラマの世界では、プログラムに潜む不具合動作をする因子を「バグ(bug:小さな虫)」と呼びます。
 そして、そのバグを退治するにあたる修正作業を「デバッグ(debug:虫、欠陥を取り除く、手直しする)」と呼びます。なんともアメリカらしい発想の表現です。
 日本ならさしずめ、「不具合改修」みたいな、仰々しい呼び方をされることでしょう。
 小さな虫だといってバカにしてはいけません。このお陰で、徹夜で作業をさせられる場合もあるのですから……。
 なので筆者は、仕事中のメールの件名に、「[不具合]」なんて入っているメールを受け取ると、ちょっと見たくなくなってしまいます。

神出鬼没で厄介なヤツ

 われわれシステム開発者だけでなく、日曜プログラマの方といえども、数百行、数千行のプログラムを作成することは、珍しいことではありません。
 ほんの「1行プログラム」ならいざ知らず、プログラムのステップが増えるほど、そこには「悪の巣窟」ともいえるバグが潜む確立が高くなってきます。
 しかし、このバグも大きく、

に分類することができ、厄介なことにその出現パターンもさまざまです。
 本稿では、システム的な(OSなどに起因するなど)バグに関連するデバッグには触れずに、もっぱら、人手によって発生するものに焦点を当ててゆきます。
 ご紹介する方法は、普段、筆者が業務において、設計/プログラミングを行ない、プログラムの動作確認を行なったりする過程で、結果として不具合を発見、発生させた時に行なう対応方法です。
 そして、その開発の一般的な流れは、図1のようになるのではないでしょうか? 図をご覧になっていただければわかるように、この流れの中でも“テスト〜デバッグ”はひとつの“ターニングポイント”になっていますね。それほど、デバッグは、重要な作業でもあるのです。

図1:アプリケーション開発の流れ
図1:アプリケーション開発の流れ

 プログラミングスタイルと同様に、デバッグの方法も人それぞれで個性が表れるものですし、なかなか決定版といえるものもないのですが、よりよいという手法は必ずあるはずです。そこで、設計/プログラミングに限らず、考えられる部分での可能性の模索の仕方を検討します。
「自分でもできる!」と思ったことは、どんどん取り入れて利用してみてください。筆者は、10年以上もの間、この仕事に携わっていますが、残念ながら、「これがデバッグの王道だ!」と呼べるほどのものをつかんでいません。このため、実際には根気よく、粘り強く、原因を追求なんてことをしていることが多いのです。
 しかし、なぜかこんな時に、不思議に頼りになるのが“勘”なのです。頼りになると表現するのは、少し語弊があるかもしれませんが、「ここが危ないんじゃないかな?」という、この勘なくして、デバッグは語れません(!?)し、この勘に助けられることも多いのも事実です。本誌の読者の方も経験されたことがあると思います。
 筆者の知る限り、優秀(?)なプログラマ、エンジニアは、例外なくこの勘が鋭く、プログラミング、デバッグなどのあらゆる場面で、その威力を発揮します。

どんなバグがあるか?

 さて、ひと言にバグといっても、その要因はさまざまです。

 そのほかにも、さまざまな要因が挙げられることでしょう。
 そこで、まず行なわなければならないことは、

です。プログラムを動作させた結果として、バグが出るわけですから、当然ながら動作する過程で、何らかの原因があるはずです。
 多くのVBプログラムは、ユーザーインターフェイスをもち、入力と出力の機能を備えていることでしょう。そして、

など、目に見える問題が発生した時に、バグとして認識されます。そして、これらの異変がトリガになり、デバッグが始まるわけです。
 しかし、業務システムのアプリケーションでは、VBといえどもバッチ的に動作するプログラムも多数存在します。
 たとえば、ある一連のワークフローに沿って、機能単位にプログラム分割を行ない、動作するようなシステムでは、最後まで結果がわからない場合があります。“最後の最後で問題が出る”というヤツです。こんな時は、中間でやり取りを行なうファイルなどに焦点を当てて、原因を特定する必要があります。
 しかし、筆者は過去に「途中経過が間違えているのに、結果が正しい」という現象に出くわしたことがあります。よくよく調べてみれば、数ヶ所の間違いが見つかったのですが、その間違い同士が相殺されて(?)正しい答えを導き出していたという、大変なレアケースでした。しかし、そう上手くいくはずもなく、違うケースでは、案の定とんでもない結果を出してくれました。
 そんなこんなで、現象が特定できたところで、今度は原因の特定を行なってゆくのです。

まずは設計の見直し

 われわれ職業プログラマからすれば、あたりまえのことなのですが、アプリケーションを開発する過程で、“設計書”(本稿では仕様書も含みます。以下設計書とします)と呼ばれる一連の成果物を作成します。

など、プログラムを製作するためには、数多くの設計書が必要とされます。
 これらの文書が残っているため、何かが起こった時は、まずこれらの成果物のチェックを行ないます。場合によっては、設計ミスがバグにつながっていることがあるからです。
 本誌の読者の中には、趣味の範囲でプログラミングを楽しんでいる方も多いことと思います。こうした方は、往々にして設計書というものを作らない、というより、作る必要をあまり感じないことでしょう(筆者ならまず作らないでしょう)。そりゃそうですね。納品とかの必要がないですし、なんといっても面倒ですから……。
 しかし、できることなら設計書に近いものでも、作ることをお勧めします。Officeなどの製品をお持ちなら、WordやExcelを利用して、持っていない場合でもテキストファイルで十分です。走り書き程度でも、要は「形にして残しておく」ことが重要なのです。
 そうすることによって、「どこが問題だったのか?」が、時間が経ったあとでも、はっきりさせることができます。いい換えれば、この部分を曖昧にしていると、何度も同じ間違いをしてしまうということにも、つながってしまいます。
「後でまとめて」と思っていると、プログラムが大きく、多機能になった時に、作るのが大変になります。気が付いたときに少しずつでも書き留める習慣を身に付けましょう。その時はわからないかもしれませんが、後できっと役に立つはずです。

シーケンス図、状態遷移図って?

 Windowsプログラマ、特にVBプログラマには、馴染みがないかもしれませんが、知っておいても損はないので、これを機会に覚えておきましょう。
 これまでのコンピュータシステムのプログラムは、“逐次駆動型”と呼ばれ、すべての制御をプログラムが握っていました。このため、ユーザーはコンピュータの指示に従い、入力/出力を行なうのです。
 何をするにも手順(コンピュータのプログラム)が優先されたので、その処理は、“フローチャート”で十分に表現することが可能でした。
 しかし、Windowsの登場により、GUI(Graphical User Interface)が一般化し、処理の主導権をユーザーが握ることになると、そのユーザーの操作(イベント)に対応するため、プログラミング形式も“イベント駆動型”という形式に変化しました。
 そうすると、どこで/どのようなイベントが発生するかを予測することは、きわめて困難になってきますし、フローチャートの中に表現しようとしても、とても表現できるものではありません。
 このため、Windowsでのプログラミングにおいても、これらの“シーケンス図”や“状態遷移図”を、設計段階で作成しておくことは、後のデバッグにおいて、有効な手段だといえるでしょう。
 そのシーケンス図、状態遷移図の簡単なサンプルをご紹介しておきますので、イメージをつかんでください(図2・3)。

図2:シーケンス図のサンプル(OKボタン押下時の処理)
図2:シーケンス図のサンプル(OKボタン押下時の処理)

図3:状態遷移図のサンプル

(1) (2) (3)

状態 Text1にフォーカスがある Text2にフォーカスがある Endにフォーカスがある
イベント
GotFocus 値のクリア 値のクリア NOP
Validate エラーあり→(1)
エラーなし→(2)
エラーあり→(2)
エラーなし→(3)
NOP
Click NOP NOP Text1、Text2未入力→(1)
Text1がエラー→(1)
Text1がエラー→(2)
エラーなし→End

 特に専用のツールなど必要はありません。手書きでも問題ありませんが、できれば方眼紙のようなものを利用したほうがよいでしょう。シーケンス図なら「レポート用紙」を横向きにすることでも大丈夫ですね。もし、Excelをお持ちであれば、Excelがいちばん便利だと筆者は思います。
 では、それぞれについて、簡単にご説明しておきましょう。
 状態遷移図の特徴は、横軸に“状態”、縦軸に“イベント”をマトリックス状に記述し、

を記述していくことにあります。このため、特定の状態だけでなく、アプリケーションが、もち得る状態すべてについて網羅し記述したほうがいいでしょう。
 そしてシーケンス図を、もう少し狭い視野で、横軸に“対象になるオブジェクト”、縦軸を“時間”として、特定の状態においての、

などを時系列に記述していきます。
 VBで利用する場合は、アプリケーション全体としてではなく、フォーム単位で、これらの文書を作成すればいいでしょう。ただし、フォーム間で、データをやり取りしているなどの場合は、それらについても記述が必要になることはいうまでもありません。
 簡単なアプリケーションでは、ここまでする必要がないかもしれませんが、最近の流行り(?)である、マルチスレッドのアプリケーションでは、設計やデバッグで、威力を発揮することでしょう。

そして机上デバッグ!

 筆者は、開発したアプリケーションをテストしている時に、バグを発見すると、まず最初に机上デバッグを行ないます。
 VBの開発環境(以下IDE:Integrated Development Environment)の特性をいかして、

と、感じる読者の方も多いことでしょうが、それがどうして原始的な方法と侮ってはいけません。出現頻度が最も高いちょっとしたミスは、多くの場合、この机上デバッグで発見することが可能なのです。
 特に、第三者が作成したプログラムについては、効果覿面※覿面に「てきめん」とルビを願います※です。
 そんな理由から、筆者はチーム開発の時に、“ワークスルー”という作業フェーズを設けて、担当者それぞれのプログラムを交換し、お互いに“コードレビュー”を行なうようにしています。
 プログラムは、作成者の思い入れの産物として作られているため、ここにちょっとした盲点が存在します。そう、自分の作ったプログラムでは、「こうしているはずだ」という思い込みの思考のため、冷静な判断ができなくなり、その結果として原因を見落としてしまいがちになってしますのです。
 こんな時の“第三者の目”は、冷静にプログラムを見てくれるため、大変有効な手段となります。
 そして、コードレビューが終わったならば、次は「設計(仕様)通りに実装されているかどうか」を確認します。設計書通りに作成されていれば正常に動作すると考えれば、当然のことですね。
 このためには、少なくともこの前のステップで、設計が正しいことが確認されていなければ何の意味もありません。このことからも設計の重要性が理解していただけることと思います。
“よいアプリは、よい設計から”ということを肝に銘じておきましょう。

それでもだめなら、IDEの機能を利用する

 ここまでの作業でも、多くのバグが見つかることでしょう。しかし、場合によっては実行してみなければ、発見できないバグというものも存在します。そんなバグを発見する時に、活躍するのがVBのIDEです。では、その機能を確認してみましょう。

IDEで提供されているデバッグ機能

 みなさんもご存知のように、VBにもデバッグ機能が搭載されています。この機能は、

を提供し、プログラム開発を支援してくれます。
 代表的なものとしては、まず“デバッグツールバー”です。
 ツールバーまたは、メニューのところで「右クリック」をしてみてください。図4のコンテキストメニューが表示されます。ここで、デバッグをクリックすることにより図5が表示されます。各ボタンの機能については、表1を参照してください。

図4:ツールバーのコンテキストメニュー
図4:ツールバーのコンテキストメニュー

図5:デバッグツールバー
図5:デバッグツールバー

 さらに、IDEの機能だけでなく、VBにはデバッグ用のステートメントが提供されています(表2)。
 特にブレークポイント、ウォッチ、ステップイン/アウトなどは、頻繁に使われる機能ですので、いろいろと試してみて「こんなものなんだ」ということを実感してみてください。
 これらの情報については、VBのヘルプ(MSDNライブラリ)によって提供され、そのほかのデバッグについての情報も満載されていますので、確認してみてください(図6)。

表1:デバッグツールバーの各機能
アイコン 名前 機能
開始/実行/継続 開始/実行/継続 アプリケーションの実行を開始する。アプリケーションが中断モードの場合には、アプリケーションの実行を再開する
中断 中断 ブレークポイントに到達した際と同様に、アプリケーションの実行を中断する
終了 終了 実行中のアプリケーションを終了する
ブレークポイントの設定/解除 ブレークポイントの設定/解除 アプリケーションのコード行に対し、ブレークポイントの設定と解除を行なう
ステップイン ステップイン アプリケーションの次の実行可能なコードを実行する。プロシージャを呼び出す場合、そのプロシージャ内でも1ステップずつ実行する
ステップオーバー ステップオーバー アプリケーションの次の実行可能なコードを実行する。プロシージャを呼び出す場合、そのプロシージャ全体を1ステップとして実行する
ステップアウト ステップアウト 現在のプロシージャを最後まで実行し、呼び出し元のプロシージャで次の行へ進む
ローカル ローカル アプリケーションが中断モードの場合に、[ローカル]ウィンドウにローカル変数の現在の値を表示する
イミディエイト イミディエイト アプリケーションが中断モードの場合に、[イミディエイト]ウィンドウでコードを実行したり、値を調べることができる
ウォッチ ウォッチ [ウォッチ]ウィンドウでは、選択された式の値を表示する
クイックウォッチ クイックウォッチ アプリケーションが中断モードの場合に、式の現在の値を表示する
呼び出し履歴 呼び出し履歴 アプリケーションが中断モードの場合に、呼び出されているが、まだ完了していないプロシージャの一覧を表示する

表2:提供されているデバッグステートメント/プロパティ
ステートメント/プロパティ 機能
Debug.Assert Assertメソッドが記述されている行で、メソッドに指定された条件にしたがってプログラムの実行を中断します
Debug.Print イミディエイトペイン内に数値、文字列などを出力します。引数に複数の式を指定する場合は、各式の間をスペースまたはセミコロン (;) で区切ります
Stop Stop ステートメントは、プロシージャ内の任意の場所に置くことができます。Stop ステートメントは、プログラムコードの「ブレークポイント」と同じ働きをします
On Error〜 エラー処理ルーチンを有効にし、プロシージャ内でのエラー処理ルーチンの位置を指定します。エラー処理ルーチンを無効にするときに使うこともできます
LastDLLErrorプロパティ 最後にダイナミックリンクライブラリ (DLL) を呼び出したときのエラーコードを返します。値の取得のみ可能です

図6:MSDNで提供される数々の情報
図6:MSDNで提供される数々の情報

デバッグ機能にもある落とし穴

 VBにも、数多くの便利なデバッグ機能があることがわかりましたが、これらも決してオールマイティーではありません。落とし穴がないか確認してみましょう。
 たとえば、ご紹介したStopステートメントを利用する場合は、注意が必要です。
 IDEで、デバッグする場合はいいのですが、コンパイルを行なったVBアプリケーション(.EXE)では、Stopステートメントが、Endステートメントと同じものとして扱われます。また、制御がStopステートメントに達すると、QueryUnloadイベント、Unloadイベントが発生せずに、ただちにプログラムの実行が終了するため、不可解なプログラム終了に悩まされてしまうことになります。
 このため、Stopステートメントは、.EXEファイルを作成する前に、必ず削除しておく必要があります。
 このあたりの実装も、VBで提供されている条件コンパイルの機能を利用すれば、特に問題は発生しないことでしょう。
 また、これらの問題点についても、VBのヘルプに記載されているため、デバッグ機能全般を一度は確認してみることをお勧めします。

いっそのこと、デバッグ機能を作ってみよう

 デバッグのコツは、“結果を残す”ということです。トライ&ビルドで、何度も挑戦してみてもいいのですが、それでは、無駄に時間だけが過ぎ去ってしまう場合があります。
 効率的に、最小限の労力で、最大の効果を狙うなら、何らかの分析するための情報が必要になることは、ご理解いただけるでしょう。
 IDEの機能を利用してデバッグをしていても、たとえばブレークポイントを利用する場合、タイミングによっては、期待したイベントが発生しないなどの問題が起きることがあります。こんな時に有効なのがログという機能です。
 残念ながら、VBが提供しているウォッチなどは、実行中にしか参照することができません。このため、ある程度「現象/原因」が特定できている場合はいいのですが、情報を収集して改めて分析したい場合などには、ちょっと使い勝手が悪いものです。
 そこで、「動作している時に監視できる/動作させた後に見ることができる」機能を作ってみましょう。

ListBoxを使った簡単ログ

 と、いうことで、デバッグ機能なるものを作ってみるのですが、あまりにも趣向を凝らしてしまっては、アプリケーションを製作するより時間がかかってしまうというような、ヘンな事態になってしまうので、最低限の機能で実装することにします。
 付録CD-ROMの\VBMAG\F_ISOディレクトリ以下にプログラムを収録してあるので、内容を確認してみてください。特殊な処理を行なっているわけではないので、本誌の読者であれば、どんな処理をしているかは、ご説明するまでもないでしょう。
 ポイントは、各コントロールのイベント単位に、必要と思われる情報を出力しています。この「必要と思われる情報」の部分がミソで、手当たり次第に出力をするとログがパンクしてしまうことになってしまいます。

ファイルにも出力してみよう。

 さて、このListBoxを使ったログにも問題がないわけではありません。
 たとえば、[終了]ボタンが押された時にも、ログの出力を行なっていますが、実際にこのプログラムを実行するとわかるように、そのログが出たとたんにプログラムも終了してしまい、結果としてログが確認できないということになってしまいます。
 こんな時は、ファイルに出力しておくと、後のデバッグでも利用できます。リスト1は、出力したログのサンプルです。日付や時刻も合わせて出力していますが、場合によっては、この日付や時刻が重要になってくる場合があるので、ログを残す際は、これらを付ける習慣をつけておいた方がいいでしょう。
 ご紹介したログという手法は、実現の仕方こそ違うかもしれませんが、さまざまな開発現場で利用され、たいへん有効な手段といえます。しかし、この有効な手段でも、問題がないわけではありません。
 場合によっては、ログの量が多すぎて、アプリケーションのレスポンスが悪くなる場合があります。そうすると、デバッグどころではなくなり本末転倒になってしまいます。何事もほどほどに、適材適所に、利用するように心がけましょう。
 今回のサンプルは、フォームモジュールで提供しているため、利用するアプリケーションの動作に依存するところがあります。これをActiveX EXEやActiveX DLLで実現して、汎用性を向上させるというのもひとつの手です。チャレンジ精神が旺盛な方は、一度試してみてください。

リスト1:ファイルに出力したログのサンプル
99/11/07 午後 9:11:09 txtInput1 GotFocus
99/11/07 午後 9:11:11 txtInput1 LostFocus:Text=aaaaaaa
99/11/07 午後 9:11:11 txtInput2 GotFocus
99/11/07 午後 9:11:13 txtInput2 LostFocus:Text=bbbbbbb
99/11/07 午後 9:11:16 Loop Cnt= 1:Ans=  1
99/11/07 午後 9:11:16 Loop Cnt= 2:Ans=  3
99/11/07 午後 9:11:16 Loop Cnt= 3:Ans=  6
99/11/07 午後 9:11:16 Loop Cnt= 4:Ans=  10
99/11/07 午後 9:11:16 Loop Cnt= 5:Ans=  15
99/11/07 午後 9:11:16 Loop Cnt= 6:Ans=  21
99/11/07 午後 9:11:16 Loop Cnt= 7:Ans=  28
99/11/07 午後 9:11:16 Loop Cnt= 8:Ans=  36
99/11/07 午後 9:11:16 Loop Cnt= 9:Ans=  45
99/11/07 午後 9:11:16 Loop Cnt= 10:Ans=  55
99/11/07 午後 9:11:19 Program End

最後の奥の手デバッグツール

 本稿は、本誌巻末にある「New Products」のような、製品紹介の場ではないので、詳細については、割愛させていただきますが、アプリケーション開発の現場に、VBが多く利用されていることもあって、市場では、数多くの優秀なデバッグツールが販売されています。
 製品によって、提供される機能はさまざまですが、最近では、見ただけではわからないような、

をレポートしてくれる製品なども登場してきました。頼もしい限りです。製品は、スタンドアロンで使用するものから、クライアント/サーバー(C/S)の環境で、使用するものまで数多く提供されています。
 また、これらの製品は、単独利用としてだけではなく、VBのアドインとして動作できる製品も多く、シームレスな感覚で利用できるため、作業効率のアップにもつながることでしょう。
 本当に行き詰まった時には、たいへん有効な手段だと筆者は感じます。興味がある方は、本誌巻末の「New Products」をご覧ください。

最後に

 バグとひと言にいっても、出現パターンや難易度などさまざまです。場合によっては、回避できないこともあり得ることでしょう。しかし、システム開発の世界では、現実的にこの厄介な“虫”のおかげで、アプリケーションの品質が左右されることも事実なのです。
 お金を出して買う側の立場になって考えてみれば、容易に理解することができますね。このため、あらゆるソフトウェアは、バージョンアップやアップデートパッチを繰り返し、その完成度を高めてゆくのです。
 本稿では、VBを主体にご説明してきましたが、筆者が行なう普段のデバッグは、ICE(In-Circuit Emulator)とアセンブラソースを利用することがほとんどなので、VBでデバッグを行なう時は、「なんて楽なんだ」って感じてしまいます。本当にいい時代になったものです。
 みなさんも、VBが提供するデバッグ機能を駆使して、どんどん素晴らしいプログラムを作ってください。
 しかし、開発ツールが進化したからといって、それでバグがなくなるわけではありません。そう「バグを作るのは人」なのです。この基本的な意識に立ち戻って、本稿でご紹介した方法を試してみてください。
 バグはVBに限った特別なものではありません。そんなこともあり、本稿ではVBに限定しないで、もう少し大きな視野で考え直してみました。よって手法自体は、そのほかのさまざま言語に応用することができることと思います。
 本稿では、バグでもほんの基本的な部分をご紹介しているに過ぎません。最近では、自社内で製作したカスタムコントロールや、サードパーティ製のカスタムコントロールなどを利用したり、通信機能を実装したりで、実際の開発現場では、もっと複雑な事象が影響しあって発生するバグも多いことでしょう。
 しかし、結果(バグ)があるなら、原因があるはずです。その原因追求の中のどこかで、本稿でご紹介した方法が、みなさんのお役に立つことを祈っています。
 最後に、バグが出たからといって、気を落としてはいけません。場合によっては、そんなことをいっていられない事態になっている場合もあることでしょうが(現在の筆者がそうだったりします)、パズルを解くつもりで、絡んだ糸をひとつずつほぐしてゆきましょう。そうすれば、必ず答えが見つかるはずです。


VB Magazine ライブラリ| Visual Basic WorkGroup
int21 ホームページ| PCDN ホームページ

PCDN
Copyright (c) 1998 int21 CorporationAll Rights Reserved.
For questions or comments, please send mail to: pcdn@int21.co.jp