Visual Basic 6.0 初級講座 |
第24回 デバッグ
実際にプログラムを作ってみると一発で成功することはまずありません。たいていどこかでエラーが出たり、思ったとおりに動かなかったりします。今回はこのような現象に有効に対応できるVBの便利な機能について解説します。
1.バグとは
昔、コンピュータの中に蛾(が)が入り込んだためにコンピュータが動かなくなったことがあったそうです。そのときのプログラマは蛾を取る作業を行って無事にコンピュータを動かすことができるようになりました。
この故事から「エラーの原因」のことを「バグ」(虫)というようになりました。そしてエラーの原因を除去してプログラムが無事動くようにする作業のことを「デバッグ」(虫取り)と言います。また、デバッグ作業を助けてくれるツールのことを「デバッガ」(虫を取る人)といいます。
VBなどの高級ツールはこの「デバッガ」がとても充実していて、一度高級デバッガに慣れてしまうと他のツールでプログラムすることが嫌になってしまうほどです。
なお、「エラー」とは「プログラムが中断」していまうようなエラーだけではなく、「プログラムが思い通りに動かない」という「論理エラー」も含みます。デバッグはこの両方に対して使う言葉です。
2.デバッグモードへの移行
では具体的な説明に入ります。
VBには「実行モード」と「デザインモード」の2つがあることは皆さんお分かりでしょう。「実行モード」は開始ボタンを押して実際に作ったプログラムを実行させている状態のことを指します。「デザインモード」とはフォームにボタンを配置したり実際のコードを打ち込んだりしている作業状態を指します。
デバッグはこれらとは別に「デバッグモード」の時に行うのが普通です。それではデバッグの入り口となるこの「デバッグモード」へはどのようにして移行するのでしょうか。
移行の方法はいくつかあります。
@自動的に移行する場合
プログラムの実行時にエラーが原因でプログラムが中断すると自動的に(または選択的に)デバッグモードに移行します。このときエラーメッセージが出て、エラーがある行が「黄色」で表示されますからデバッグモードに移行したということが一目でわかります。
(写真1:はじめデバッグモードでは中断された行が黄色く表示される)
注意:VBでは制御できないエラーが発生した場合はデバッグモードに移行しない場合もあります。この時多くのケースではプログラムはVBごと強制終了されてしまいます。ただし、普通にプログラムしている限りこのようなことは滅多に起こりません。
A一時停止ボタンを押した場合
プログラムの実行中に「一時停止ボタン」をクリックするとデバッグモードに移行します。このとき実行されようとしていた行が「黄色」で表示されます。
待機状態で「一時停止ボタン」をクリックした場合は、どの行も黄色にはなりません。
Bブレイクポイントに到達した場合
プログラマはプログラムを停止して「デバッグモード」に移行したい行に「ブレイクポイント」というものを設定することができます。プログラムの実行がこのブレイクポイントを設定した行に到達するとデバッグモードに移行します。このときブレイク行が「黄色」く表示されます。
ブレイクポイントを設定するにはブレイクポイントを設定したい行にカーソルを移動してからキーボードの F9 を押します。もう一度 F9 を押すとブレイクポイントを解除できます。
ブレイクポイントが設定されている行は茶色で表示されます。また左に茶色い丸が表示されます。この茶色い丸の部分をクリックしてもブレイクポイントを設定/解除することができます。
(写真2:ブレイクポイントを設定した行は茶色く表示されるのですぐわかる)
CStopステートメントが実行された場合
プログラムの途中に Stop と記述すると その場所でプログラムの実行を一時停止してデバッグモードに移行できます。ブレイクポイントの場合と同じようなものですね。
ブレイクポイントとの違いはプログラムの中に組み込んで使える点です。このため次のようなことができます。
If X = 0 Then Stop
この例では X = 0 の時にのみデバッグモードに移行します。
さらに条件付コンパイルと組み合わせて使う技もあります。
Dウォッチ式がTrueになった場合
これは後で説明します。
なお、デバッグモードから実行モードの戻るには「実行ボタン」をクリックするか、キーボードの F5 を押します。デバッグモードからデザインモードに戻るには「終了ボタン」をクリックします。
3.デバッグの例
「デバッグモード」では何がエラーの原因になっているのか突き止めることが目的となります。そのためにいろいろな情報を収集してプログラムの状態を調べることができます。
ここからは具体的なデバッグ作業を通して説明しましょう。フォームにテキストボックス(Text1)とコマンドボタン(Command1)を配置して次のようにプログラムしてください。
'注意! このプログラムはデバッグの練習用のものなので まちがいだらけ です。 Private Sub Command1_Click() MsgBox IsURUU(Text1.Text) End Sub |
Private Function IsURUU(TargetDate As
Date) As Boolean Dim TargetYear As Long TargetYear = Year(TargetDate) If TargetYear Mod 4 = 0 Then If TargetYear Mod 100 = 0 Then If TargetYear Mod 400 = 0 Then IsURUU = False Else IsURUU = True End If Else IsURUU = True End If End Function |
このプログラムはテキストボックスに入力された年月がうるう年であるかどうかを判定します。入力した日付がうるう年ならTrue、ちがうならFalseを表示します。
なお、うるう年であるための条件は次のとおりです。
@4で割り切れる年
Aただし、100で割り切れて400で割り切れない年を除く
このことにより、2000年はうるう年ですが2100年はうるう年ではありません。2100は100で割り切れて、400で割り切れないからです。
さて、プログラムを実行してテキストボックスに 2000/1/1 と入力してください。2000年はうるう年ですから True と表示されればOKです。
ところが、コマンドボタンをクリックするとエラーが発生して自動的にデバッグモードに移行してしまいました。何がいけないのでしょうか。黄色くなっているのは Private Function IsURUU(TargetDate As Date) As Boolean の行です。この行のどこかにエラーがあるのでしょうか? しかしエラーメッセージは 「If ブロック に対応する End If がありません」です。そこで IsURUU関数内を良く見てみると確かに If の数が End If より1つ多いです。どこかに End If を1つ追加しなければなりません。どこでしょうか?
この点を訂正すると次のようになります。
Private Sub Command1_Click() MsgBox IsURUU(Text1.Text) End Sub |
Private Function IsURUU(TargetDate As Date) As Boolean Dim TargetYear As Long TargetYear = Year(TargetDate) If TargetYear Mod 4 = 0 Then If TargetYear Mod 100 = 0 Then If TargetYear Mod 400 = 0 Then IsURUU = False Else IsURUU = True End If Else IsURUU = True End If End If 'この行を新しく追加します。 End Function |
訂正したら、実行ボタンをクリックして実行モードに戻りましょう。Falseと表示されましたね。エラーが修復されたということです。
しかし、安心していけません。2000年を入力してのですから True と表示されるべきです。つまり「プログラムが思ったとおりに動作していない」のです。「論理エラー」ですね。
論理エラーを追跡するにはブレイクポイントが有効です。TargetYear = Year(TargetDate) の行にブレイクポイントを設定しましょう。ブレイクポイントを設定するにはその行にカーソルを移動させてキーボードの F9 を押します。
それでもう一度 2000/1/1 と入力してボタンを押してください。ブレイク行(ブレイクポイントを設定した行)が黄色く表示されてデバッグモードに移行したことと思います。
ここで「デバッグツールバー」を表示させましょう。それには[表示]メニューから「ツールバー」を選択して「デバッグ」をクリックします。このツールバーを使って「ステップ実行」というものを行うと1行ずつプログラムを実行させることができます。どういうことなのか文章で説明してもまずわかりにくいと思いますので実際に「ステップ実行」をしながら読んでください。
(写真3:デバッグツールバー。デバッグに使う機能を簡単に呼び出せる)
「ステップ実行」の簡単な方法は「ステップイン」ボタンをクリックすることです。早速「ステップイン」ボタンをクリックしてください。今度は If TargetYear Mod 4 = 0 Then の行が黄色くなったでしょう。 TargetYearは変数なのですがその値はいくつでしょうか? 2000/1/1 と入力したのだから 2000 となっているはずです。これを実際に確かめるにはマウスカーソルを TargetYear と書いてある文字の上に移動させてください。TargetYear = 2000 と表示されます。このようにデバッグモードの時に実際の変数の値を確認することができます。 このような確認の方法を「クイックウォッチ」といいます。
さて、TargetYear = 2000 ということは TargetYear Mod 4 = 0 は True です。「ステップイン」ボタンをクリックしてください。確かに If TargetYear Mod 100 = 0 Then の行に制御が移りましたね(黄色く表示されるのでわかる)。ここまでは思ったとおりに動いているわけです。
TargetYear = 2000 ですからここも True ですね。また「ステップイン」ボタンをクリックしてください。予想通り If TargetYear Mod 400 = 0 Then に制御が移ります。
もちろん TargetYear Mod 400 = 0 も True ですから 次のはこの下の行の制御が移るはずです。「ステップイン」ボタンをクリックしてください。はたしてその通り IsURUU = False に制御が移りました。
さて、バグの原因はわかりましたか? この行は IsURUU = True と書くべきだったわけですね。直してしまいましょう。そうると IsURUU = False とあったところも IsURUU = True に直す必要がありますよ。
直すとこうなります。
Private Sub Command1_Click() MsgBox IsURUU(Text1.Text) End Sub |
Private Function IsURUU(TargetDate As Date) As Boolean Dim TargetYear As Long TargetYear = Year(TargetDate) If TargetYear Mod 4 = 0 Then If TargetYear Mod 100 = 0 Then If TargetYear Mod 400 = 0 Then IsURUU = True Else IsURUU = False End If Else IsURUU = True End If End If End Function |
これでめでたく正しくうるう年を判定できるようになりました。
4.さらなるデバッグ イミディエイトウィンドウ
上の例でさらに説明を続けます。このプログラムの致命的な欠点はすぐに露呈(ろてい。現れること)します。例えばテキストボックスに "Hello" を入れた場合実行時エラーが発生してプログラムが中断されてしまうのです。
この原因はデバッガで探るまでもなく明らかです。エラーメッセージにも表示されるように「型が一致しない」のです。これが何のことだかわからない人はデバッガで調べてみるのも良いかもしれません。とりあえずエラーメッセージとともに表示される「デバッグ」ボタンをクリックしてデバッグモードに移行してください。
MsgBox IsURUU(Text1.Text) のところで中断したことがわかります。まぁこれだけわかれば普通は大丈夫なのですがここでは解説のためにさらにデバッグ作業を続けます。
まず、Text1.Textという文字列にマウスカーソルを合わせます。そうすると Text1.Text = "Hello" と表示されることでしょう。このことからわかるようにプログラムは IsNull("Hello") を実行しようとした時にエラーになったか、 MsgBox関数を実行しようとした時にエラーになったかのどちらかです。
さて、ここでイミディエイトウィンドウを表示させましょう。[表示]メニューから[イミディエイトウィンドウ]をクリックすると表示されます。
このイミディエイトウィンドウに
Print IsURUU("2000/1/1")
と入力して Enterキーを押してください。Trueと表示されますね。このようにイミディエイトウィンドウは1行ずつその場で書いた命令を実行させることができます。そこでさらに、
Print IsURUU("Hello")
と入力して下さい。今度はエラーが発生しましたね。やはりプログラムはこの IsNull("Hello") を実行しようとした時にエラー になったということがわかります。
ついでに
Print IsURUU(2000)
も実行してみてください。これは2000年だからTrueかと思いきや False と表示されます。この現象は今後の課題として覚えて起きましょう。
ところで、このエラーはどうやって回避したらよいでしょうか?
いろいろあると思いますが私は次のようにしてみました。
Private Sub Command1_Click() If IsDate(Text1.Text) = False Then 'この4行を追加します。 MsgBox "日付を入力してください。" Exit Sub End If MsgBox IsURUU(Text1.Text) End Sub |
Private Function IsURUU(TargetDate As Date) As Boolean Dim TargetYear As Long TargetYear = Year(TargetDate) If TargetYear Mod 4 = 0 Then If TargetYear Mod 100 = 0 Then If TargetYear Mod 400 = 0 Then IsURUU = True Else IsURUU = False End If Else IsURUU = True End If End If End Function |
修正したところで実行すると今度はまず満足な結果となりましたね。
なお、うるう年であるか判定するプログラムは熟練(じゅくれん。よくなれていること)のプログラマなら次のように書くかもしれません。
IsURUU = (Day(CDate(Year(TargetDate) & "/3/1") - 1) = 29) |
この1行だけで判定することができます。ためしにIsURUU関数の中身をこの1行と置き換えて実行してみてください。
5.ウォッチウィンドウ
今度は別の例で説明します。
フォームにコマンドボタンを配置して次のようにプログラムしてください。
'注意! このプログラムは間違いだらけです!
Private Sub Command1_Click() |
このプログラムは画面に複数の円を描画します。それぞれの円は中心が共通で、半径はことなり、色は徐々に変化します。半径が0より下になるとエラーになるので Do Loop のところで続行条件を While K > 0 (Kが0より大きい場合には続行)にセットしています。
実行する時はフォームを最大化してからにすると良いでしょう。どうです?ちょっと渋いグラフィックが表示されましたか? でもエラーも出ましたね。エラーメッセージは 「0で除算しました」 です。さて・・・・?
このプログラムで除算しているところは 1000 Mod K の1個所だけです。しかし、このループは While K > 0 となっているのでKが0になることはないように思えます。それでもデバッグモードにして K のところにマウスカーソルを合わせてみると 「K = 0」 と表示されています。思ったとおりには動いていないようです。
そこで一旦終了させてループ内の K = K - 1 にブレイクポイントを設定してプログラムの動きを見てみましょう。実行してください。ブレイク行でデバッグモードに移行したらステップインでプログラムの動きを追いましょう。ループ処理なんでぐるぐるまわりますね。いつ K = 0 になるのでしょうか。多分ループを1000回まわった時があやしいですね。そうはいっても1000回もステップインを押すのはとても面倒です!
ここはあきらめて一旦終了させましょう。
K = 0 になった時だけブレイクしてくれるようなブレイクポイントは設定できないものでしょうか。残念ながらVBではこのようなデバッグ機能はありません(Delphiにはあります)。しかし、「ブレイクポイント」にこだわらなければこれと同じことができます。それがウォッチ式です。[表示]メニューから[ウォッチウィンドウ]を表示させてください。ウォッチウィンドウ内で右クリックして「ウォッチ式の追加」を選びます。
ここで、「式」の欄に K = 0 と入力して、ウォッチの種類のところで「式が True の時に中断」を選択します。「OK」を押すとこのウォッチ式が追加されます。これで K = 0 になったときプログラムがどこを実行していてもすかさずデバッグモードにこうすることができます。
早速実行してコマンドボタンを押してください。いきなり止まりましたね。最初はどの変数も 0 なのでいきなりウォッチ式 K = 0 が True になってデバッグモードに移行したというわけです。これじゃ不便なので また一旦終了してウォッチ式を K = 1 に直しましょう。ウォッチ式を直すにはウォッチウィンドウ上で右クリックしてウォッチ式の編集をクリックします。
これで実行するとループ内で K = 1 になった瞬間にデバッグモードに移行することができましたね。この後ステップ実行(ステップインを何度かクリックして実行していくこと)をすると何が原因かわかります。While K > 0 のチェックは K = K -1 が実行されて 割り算が行われた後で入るので K = 0 の割り算を阻止できていないのでした。
原因がわかれば修正も簡単です。
次のように修正すると良いでしょう。
Private Sub Command1_Click() Dim K As Long Dim CenterX As Long Dim CenterY As Long Me.BackColor = vbBlack CenterX = Me.ScaleWidth \ 2 CenterY = Me.ScaleHeight \ 2 K = 1000 Do K = K - 1 Me.Circle (CenterX, CenterY), 40 * K, 1000 Mod K Loop While K > 1 'この行を修正 End Sub |
なお、熟練したVBプログラマーはこの処理を次のように書くことでしょう。
Private Sub Command1_Click() Dim K As Long Dim CenterX As Long Dim CenterY As Long Me.BackColor = vbBlack CenterX = Me.ScaleWidth \ 2 CenterY = Me.ScaleHeight \ 2 For K = 1000 To 1 Step -1 Me.Circle (CenterX, CenterY), 40 * K, 1000 Mod K Next K End Sub |
6.補遺1 イミディエイトウィンドウ
以上、説明した他にもイミディエイトウィンドウを使った便利な技を紹介します。
@デザインモードで使う
イミディエイトウィンドウは他のデバッガと違ってデザイン時にも使えます。
たとえば、「Mid関数の使い方ってこれでいいんだっけ?」とか思った時は試しにイミディエイトウィンドウで実験することができます。
A伝統の 「?」
イミディエイトウィンドウでは ? と打ち込むと Print の意味になります。
たとえば、? Sqr(23) は Print Sqr(23) と同じ意味です。
これはBASIC言語の伝統です。
B複雑な処理もできる
イミディエイトウィンドウでは Print命令 しか実行しない という人も多いようですがもっと複雑なこともできます。
たとえば、次の例ではパソコンにつながっているプリンタの名前を列挙します。
For Each V In Printers: ? V.DeviceName: Next V
Cプログラムからの出力
プログラム中に Debug.Print メソッドを使うと イミディエイトウィンドウに出力することができます。
たとえば、次の例ではイミディエイトウィドウに時刻を出力します。
Debug.Print Now
7.補遺2 ウォッチウィンドウ
ウォッチウィンドウについてもいくつか補足します
@式のウォッチ
ウォッチウィンドウはデバッグモードの時にすばやく変数や式の値を確認するために使うことができます。ウォッチウィンドウを使わない場合はマウスポインタを式に合わせると式の内容を確認できますが確認したい式や変数がたくさんあるときに便利です。
方法は、ウォッチ式を追加する時にウォッチの種類で「式のウォッチ」を選択します。
A値が変化したら中断
式や変数の値が変化したらデバッグモードに移行するようにセットすることもできます。
方法は、ウォッチ式を追加する時にウォッチの種類で「式の内容が変化した時に中断」を選択します。
Bウォッチ式を簡単に追加
プログラムで、ウォッチ式に追加したい式を選択状態にしてウォッチウィンドウにドラッグするとその式をウォッチ式に追加することができます。
8.補遺3 その他
その他のデバッグに関する補遺です。
@Stopステートメントのコンパイル
コンパイル後は Stopステートメントは Endステートメントと同様に扱われます。注意してください。
A呼び出し履歴
複雑なプログラムはイベントプロシージャから関数を呼び出すことが多いです。さらに関数から関数を呼び出したりしますからデバッグするとき中断した関数がどこから呼び出されたか知るのは大変です。
そこで呼び出し履歴を使うと一目でそれがわかります。
呼び出し履歴を見るにはデバッグツールバーの「呼び出し履歴」をクリックするか、[表示]メニューから[呼び出し履歴]を選択します。
Bローカルウィンドウ
ローカルウィンドウを使うと中断中のプロシージャ内のさまざま情報を一度に表示させることができて大変便利です。
ローカルウィンドウを使うにはデバッグツールバーから選択するか、[表示]メニューから選択します。
9.最後に
今回はVBに付属するデバッグツールの使い方を一通り解説したつもりです。いかがでしたでしょうか。実際にこれらのツールを使いこなしてデバッグを進めていくには少し「なれ」が必要かと思います。
ちなみに、私はイミディエイトウィンドウが大好きでイミディエイトウィンドウをデバッグのメインに使っています。