今回は、エクセルVBA開発におけるデバッグ(debug)について話したいと思います。バグ(bug)は元々、害虫を意味する英語ですが、プログラム開発におけるバグとは、不具合のことを指します。この不具合を修正・改善することを、虫取りに、なぞらえてデバッグと呼びます。
『一生懸命、作成したプログラムも、いざ、動かしてみると、まったく思った通りに動かない!?』って経験、ありませんでしょうか? 安心してください。プロのシステムエンジニアでも、いきなり思ったとおりに動作するプログラムを書ける人は、ほとんどいないと思います。
とうことで、デバッグの基本的なテクをレクチャーするため、サンプルプログラムを作成してみたので、以下からダウンロードしてみてください。
サンプルのダウンロード
サンプルの内容は、円柱の体積を求める、いたって単純なプログラムです。
表に、底面の円の半径の値と、円柱の高さを入力し、【体積(VBA)】ボタンを押すことで、与えられたパラメータからプログラムにより、円柱の体積を算出する仕様となっています。また、プログラムで算出した体積値の正誤を確認できるよう、セルの式設定により算出した体積の値も、同時に、表示するようにしてあります。
それでは、一度、実際に【体積(VBA)】ボタンを押下して、プログラムを実行してみましょう。例として、ここでは、半径1、高さ2をパラメータとして入力してみました。
パラメータが入力できたら、【体積(VBA)】ボタンを押してみてください。円柱の体積は、底面積×高さ ですので、(円周率×半径×半径)×高さで算出できます。ちなみに、円周率は3.14を使用するものとします。
すると、以下のように体積(VBA)の箇所に6と出力されました。セルの計算式で求めた値が6.28で、プログラムで、求めた値が6となってしまっています。どうも、プログラムにバグがあるようです。
それでは、プログラム中のどこにバグがあるのかデバッグしてみましょう。手始めに、デバッグの第一歩として、ブレークポイントを設定してみましょう。ブレークポイントとは、プログラムを実行した時に、デバッグのために、プログラミングの実行を一時的に停止させるポイントのことです。
ブレークポイントを設定すると、その箇所に実行が移ってきた時に、CPUがint3命令を実行し、通常のプログラム動作に対して、ソフトウェア割り込みを発生させ、一時的にプログラムの制御を奪うことができます。それにより、プログラムのコンテキスト(変数やメモリ、コールスタックの情報 etc.)を確認することができ、デバッグが可能になります。ちなみに、int3のintは、整数を表すintegerではなく、割り込みを表すinterruputの略です。この命令はCPUが持っている命令ですので、VBAで直接記述することは不可能なのですが、間接的に、Debug.Assert Falseを呼び出すことで、同等の動作をさせることが、可能です。
サンプルプログラムでは、ColumnarVolume関数で円柱の体積を算出していますので、この関数を呼び出している以下の行にブレークポイントを設定してください。ブレークポイントの設定は【F9】キーで行ないます。また、一度設定した箇所で再度、【F9】を押すと設定を解除することができます。今回は1箇所だけですが、複数の箇所にブレークポイントを設定することも可能です。
それでは、ブレークポイントが設定できたら、元のエクセルの画面に戻って、再度プログラムを実行してみましょう。【体積(VBA)】を押します。
すると、以下のように、関数の呼び出し直前(ブレークポイントの設定位置)で、プログラムが中断されます。
少し脇道にそれますが、メニューの【表示】-【呼び出し履歴】を選択すると、以下の画面が表示されます。
これを見ると、ボタン押下時のイベントCBtnDebug_Click関数から、体積算出のColumnarVolume関数が呼び出されていることが、分かります。この画面は、関数の呼び出し履歴の一覧で、関数が呼び出された順番に表示されており、それぞれの関数名をダブルクリックすると、その関数にジャンプします。これは、コードを再確認する場合に非常に便利な方法ですので、是非、覚えておいてください。
呼び出し履歴は、スタック上に積まれた関数の戻り先のアドレス値から、関数を特定し、呼び出し履歴を表示しています。この呼び出し履歴のことをコールスタックとも呼びます。また、この履歴を辿りながら、スタック上の値を確認し、デバッグすることをスタックトレースと呼びます。
本題に戻りデバッグを続けたいと思います。まず、エクセルの表に入力した、半径rと高さhが正しく取得できているかどうか確認してみましょう。
変数の値を確認するには、メニューの【表示】より、【ローカルウィンドウ】もしくは、【ウォッチウインドウ】を表示します。
ここでは、【ウォッチウインドウ】を表示することにしてみます。【ローカルウィンドウ】を表示すれば、ローカル変数を一度にすべて確認できるのですが、表示を必要な変数のみにしたい場合、または、グローバル変数を確認する場合には、【ウォッチウインドウ】の方を使用してください。
ウォッチウインドウに、変数rと変数hをそれぞれ選択し、ドラッグ&ドロップして、値を確認してみてください。私の場合は、半径に1、高さ2を指定したのですが、上図を見ると、それぞれその値が正しく取得できていることが確認できます。
※ちなみに、変数をマウスで選択して、暫く待っていると変数の値がポップアップ表示されますので、それにより確認することもできます。
それでは、次に、関数内のプログラムコードをチェックしてみましょう。関数内にステップ実行して、処理を移動させてみます。ステップ実行は、【F8】キーを押すことで実行されます。
【F8】キーを押すと、プログラムの実行位置が、1step進み、上図のように関数内に突入したことが分かると思います。この【F8】キーのステップ実行のことを、特にステップイン実行と呼びます。関数内に入る(イン)ことが出来るという意味合いで、そう呼ばれていると思われます。関数内に処理が移行できるのは、ユーザ定義の関数のみ(自分で記述した関数)で、Windows APIや、VBA開発用にあらかじめ用意されている関数には、ステップイン出来ません。これらの関数の直前で、ステップ実行(【F8】)した場合は、関数を通り越して(関数自体の処理は実行されています)、実行位置が次のステップに移動します(ステップオーバー実行と同等)。
それでは、更にステップ実行を継続してみましょう。【F8】キーを3回を押すと、下記の位置に実行位置が移動します。
円柱の体積は、底面積×高さという見方もできますので、中間値として底面積Sを算出しています。また、その値をDebug.Printメソッドで出力するようにしてあります。このように中間値の値や処理途中の情報を残しておくと、不具合の原因を突き止めるヒントになりますので、非常にデバッグの効率が上がります。Debug.Printにより出力した情報は、【イミディエイトウインドウ】で確認(【CTRL】+【G】)できますので、一度確認してみてください。
Debug.Printの情報は、プログラムの状況を把握するのに便利なのですが、文字を表示するということは、処理が発生しており、すくなからず時間が掛かっているということを意味しています。ですので、パフォーマンスを考えると、システム開発中はともかく、プログラムが安定した時点で、Debug.Printを削除または、コメントアウトすることが望ましいと思われます。
【イミディエイトウインドウ】でも、底面積sの値が、確認できるのですが、練習のため、【ウォッチウインドウ】にドラッグ&ドロップして、値を確認してみてください。
するとどうでしょう。sの値が3になっていることが分かります。底面積は、3.14×1×1ですので、3という整数値はおかしいような気がします。
ということで、この辺りのソースコードを良く確認してみましょう…
よく見ると、sの宣言が整数型のLong型になっていることに気付いたでしょうか? バグは、データ型の指定ミスにより、中間値である底面積の値の少数点以下が切り捨てられしまっていることが原因でした。
それでは、変数sを整数型のLong型から、少数点が扱える実数型のDouble型にバグ修正し、再度、プログラム実行して、バグが改善されているかどうか確認してみましょう。
エクセルの画面に戻り、【体積(VBA)】ボタンを押下すると、ブレークポイントである以下の箇所で、再び、プログラムが中断されます。
おそらく、Double型に変更したことで、バグは修正されていると思うので、今度は、関数内には入らず、ステップオーバー実行してみましょう。関数内に入らず、ステップオーバー実行したい場合は、【SHIFT】+【F8】キーを押下します。実際に、ステップオーバー実行すると関数を通り越し、Debug.Printの行に処理が移動します。
さらに、ステップ実行(【F8】)して、イミディエイトウインドを確認してみてください。体積であるvolの値が、6.28になっていることが、確認できるはずです。また、ウォッチウインドウにvolの値をドラッグ&ドロップして、確認しても6.28になっているはずです。
最後に、エクセル画面の出力結果を確認したいので、ステップアウト実行(【F5】もしくは、【CTRL】+【SHIFT】+【F8】)を行い、ステップ実行を終了します。他にブレークポイントを設定しているとその箇所で、プログラムが再度、中断されますが、特に設定がなければ、ステップ実行処理が終了(アウト)し、表の体積(VBA)の箇所に6.28が出力され、セルの計算式で出力した6.28と同等になっていることが、確認できます。以上が、今回のサンプルの一通りのデバッグ手順となります。
デバッグに必要な最小限の情報を今回の豆知識で盛り込んだつもりです。今回の内容をマスターすれば、ある程度、どのようなバグにも対応できるようになると思います。ポイントは以下のようなところだと思いますが、このあたりが、身につくと、開発効率がグングン上がると思います。
次回も、デバッグについて紹介するつもりで、内容は少し、応用編のようなものを考えています。
以下から、今回の豆知識で紹介した内容のエクセルファイルがダウンロードできます。
サンプルのダウンロード
ご意見・ご要望等ありましたら、画面最下部のメールアドレスまでご連絡ください。