Visual Basic 初級講座 |
Visual Basic 中学校 > 初級講座 >
実際に動かないプログラムを使ってデバッグの手順・方法を説明します。独学でVBを勉強している方は絶対に今回の説明を読んでおいた方がよいと思います。あなたは適切な方法でデバッグしていると言い切ることができますか?
概要 ・ 「大きな古時計」を演奏するプログラムをデバッグします。ステップ実行やウォッチ、イミディエイトウィンドウの使いどころを逐一説明し、順を追ってプログラムを完成させます。 |
今回は実際に動かないプログラムを使用して、動くように改造する手順を説明します。特に「どこがおかしいか調べる方法」が中心となります。コードのどこが悪いとか、メソッドの使い方がおかしいとかそういう話はここでは重要ではありません。
ブレークポイントやウォッチ、イミディエイトウィンドウといったツールをどのように活用してプログラムの問題点を調べることができるのかを説明します。
それから、今回はわざとエラーがたくさん発生するようになっていますから、実際に試す場合は安全のために、作業中のファイルはすべて保存しておいてください。そして、ここでの説明を一通り試すか、途中まで試してやっぱりやめる場合はVBを閉じた後でパソコンを再起動してください。
前置きが長いですが、ちょっと辛抱してください。
今回使用するプログラムはメロディを奏でるプログラムです。あなたのパソコンで「大きな古時計」を楽しみましょう。しかも、なんと音源がないパソコンでも音がなるように作ってあります。スピーカーがなくても音が鳴ります。音源もスピーカーもなくてどうやってメロディーが流れるのか興味ありませんか?
※ただし、すべての環境でテストすることは不可能ですので、ひょっとしたら音が出ないパソコンもあるかもしれません。その時はご容赦ください。
デバッグを説明する前にこの音が出る仕組みまでを簡単に説明しておきます。その方がデバッグの説明のところでも話がわかりやすいことでしょう。
まず、新しいプロジェクトを作成してください。
次に音を鳴らすために私が作ったクラスをダウンロードしてください。
VB2005用
Sound2005.lzh
(9KB) VB.NET2003用 Sound2003.lzh (6KB) ※ VB.NET2002を使用されている場合は、.NET Framework 1.1がインストールされていればVB.NET2003用のものが使用できます。.NET Framework 1.1はマイクロソフトのサイトからダウンロードできます。 ソースコードはこのページの一番下からダウンロードできます。 |
ダウンロードしたファイルを解凍するとSound.dllというファイルが作成されます。このファイルをどこかのフォルダに移動してください。どこでもいいので、ここで悩まないで下さい。
次にこのdllに参照を設定します。
参照設定をするにはVB2005では[プロジェクト]メニューの[参照の追加]をクリックし、表示されるウィンドウの[参照]タブをクリックして先ほどのSound.dllを指定してください。
VB.NET2002、VB.NET2003では[プロジェクト]メニューの[参照の追加]をクリックし、表示されるウィンドウの[.NET]タブをクリックして「参照」ボタンを押して先ほどのSound.dllを指定してください。
ここで、単純に音を鳴らしてみましょう。
フォームにボタンを貼り付けてクリックイベントに次の通りプログラムしてください。
Dim
Player As New
RLaboratory.Windows.Sound
'リードオルガンを選択 Player.PlayString("A") 'ラ Player.Close() |
■リスト1
これを実行すると「ラ」の音がなります。実際に音を鳴らすにはこのようにPlayStringメソッドを使います。
音色はリードオルガンを指定していますが、音源がない環境で実行するとどの音色を指定してもビープ音が鳴ります。音源がある環境の場合は音色をいろいろ変えて楽しむこともできます。
"A"はラを意味しています。音階とアルファベットの対応は次の通りです。
C | D | E | F | G | A | B |
ド | レ | ミ | ファ | ソ | ラ | シ |
休符は"R"です。
オクターブはPlayStringメソッドの第2引数で指定できます。1を指定すると1つ高いオクターブ、-1を指定すると1つ低いオクターブとなります。
シャープとフラットは「+」と「-」で表現します。たとえば、ミのフラットを鳴らすにはPlayer.PlayString("E-")となります。
さらに8部音符は「8」、16部音符は「16」のように表現できます。これによってファのシャープを8部音符で鳴らすにはPlayer.PlayString("F+8")となります。数字を指定しない場合は4部音符となります。
試しに次の楽譜を演奏させて見ましょう。この楽譜は「夏の思い出」の最初のフレーズです。「夏がくーれば思い出す〜」という歌です。
■画像1:「夏の思い出」の楽譜
楽譜がわからない方のために文字でも書き込んでおきました。このフレーズを演奏するプログラムは次の通りになります。
Dim
Player As New
RLaboratory.Windows.Sound
'リードオルガンを選択 Player.PlayString("F+8")
'ファ# Player.Close() |
■リスト2
楽譜と比較してみるとわかるのですがシンプルなものです。楽譜を一音ずつ演奏するだけです。
でも、こうやって一音ずつ演奏していくのはかなり大変ですよね。そこで今回の動かないプログラムが登場します。
今回のプログラムでは一音ずつではなく楽譜をまるごと文字列にして演奏します。たとえば、上の「夏の思い出」の例で行くと次のような文字列の演奏を計画しています。
"F+8F+8G8AG8F+8E8E8E8F+8G2"
これは一音ずつ鳴らしていたのをつなげただけですが、これが演奏できればプログラムは楽になります。
「夏の思い出」では出てこないのですがオクターブが変るケースも考えられますから、楽譜文字列では記号"<"で1つ上のオクターブ、記号">"で1つ下のオクターブに切り替わるようにします。
次の楽譜はキャンプファイヤーの時に良く歌われる「もえろよもえろ」です。
■画像2:「もえろよもえろ」の楽譜
この楽譜では途中で上のオクターブに切り替わる部分がでてきますから、この楽譜を文字列にすると次のようになります。
"FR8E-8D8F8B-8<C8D2>B-"
この曲はフランスの曲らしいです。
さて、楽譜文字列を演奏するためのプログラムを用意しましょう。もちろん最初の段階ではバグだらけで動きません。バグを発見し、取り除いて動くようにするのが今回の目的です。
では、新しいプロジェクトを作成してください。さきほど試しに作成したプログラムはもう使わないので閉じてください。
新しいプロジェクトでも先ほど説明したのと同じようにSound.dllへの参照を追加してください。
用意ができたらフォームにボタンを1つ配置して、このボタンのクリックイベントとして次のコードを貼り付けます。
Private Sub
Button1_Click(ByVal sender
As System.Object, ByVal
e As System.EventArgs)
Handles Button1.Click
Dim CmdSound
As String
'楽譜を表す文字列 '●前処理 '楽器・楽譜の初期設定 '音色にシンセリードを指定。
'このコメントをはずすとMIDIデバイスがあってもビープ音による演奏が行われる。
'大きな古時計の楽譜(最初のフレーズのみ) '●メイン処理 '楽譜から一音ずつ取り出して演奏する Reader = New IO.StringReader(CmdSound) Do While Reader.Peek '楽譜の最後に到達するまでループを実行 Note = Chr(Reader.Read) '楽譜の次の音階を読む。
'▼読み込んだのが音階ではなくオクターブを変える記号の場合
'▼次の文字が-+(調号)の場合は"C+", "B-"のように音階と一緒にする。
'▼次の文字が数字の場合は"C8","F+8"のように音階と一緒にする。
'▼その次の文字も数字の場合も同様。たとえば"D+16"などの場合 End If
'▼読み込んだ音を発音する。 Loop '●後処理 Reader.Close() End Sub |
■リスト3
このプログラムでは「大きな古時計」を使用しています。
ここまでの作業が完了すれば、とりあえず実行して音が鳴るのを確認できます。すぐにエラーになりますが試しに実行してみてください。
音が3つ鳴ったところでエラーになれば正常です。音が鳴らないでエラーになる場合はスピーカの音量等を確認してください。
デバッグ作業を開始する前に少しこのコードについて説明しておきます。このコードはさきほども説明したように楽譜文字列を演奏するためのものです。たとえば、"CDE"と書いてあったら「ド」、「レ」、「ミ」と順番に鳴らしたいわけです。PlayStringメソッドでは一音ずつしか演奏できないので楽譜文字列を自分で一文字ずつ取り出してPlayStringメソッドに渡す処理を書く必要があります。
Do 〜 Loopでは一音につき一回ループが回ります。"CDE"だと3回ループが回るわけです。1文字ずつ取り出してPlayStringメソッドを呼び出すだけだったらまだ楽なのですが、シャープやフラットがまざってくると、"EF+G+"のように1文字1音とは限らなくなrます。さらに、音の長さの表現も加わって"EF+8G+"のようになるかもしれません。
"EF+8G+"だとすると、"E"と"F+8"と"G+"の3音に分解して順番にPlayStringメソッドを呼び出さなければなりません。
このような事情で「一音ずつ取り出す」ために多少のプログラムが必要になってきます。
この「多少の処理」を詰め込んだのが上記のプログラムです。もちろん、この状態ではバグがあるので動作しません。
ここからが今回のメインテーマであるデバッグになります。
まずは音が3つ鳴ったところで発生するエラーの原因を突き止めましょう。
もう一度エラーを発生させて状況を確認すると次のようにPlayStringメソッドの実行のところでエラーになるのがわかります。
■画像3:エラー
画像はVB2005のものですが他のバージョンのVBでもだいたい同じような内容が表示されます。エラーメッセージは「音階は"C", "D", "E"のように指定する必要があります。」となっていますから、PlayStringメソッドに変な文字列が渡された可能性が考えられます。
この状態のまま、実際に第1引数のNoteのところにマウスカーソルをあてて変数の内容を確認してみましょう。次のように表示されます。
■画像4:とりあえず変数の値を確認
この時点で、変数Noteの値が"+88"になっているようです。確かにこれはおかしいですね。4音目だから"G8"になるはずだったのですが…。
3音目まではちゃんと音が出ているのですから、どこかで楽譜の切り分け方が間違っていることが想像できます。
ループの先頭のほうにブレークポイントを設定してステップ実行しながらどこからおかしくなるか確認してみましょう。
■画像5:ブレークポイント
実行するとブレークポイントのところでいきなり止まります。Noteの値がバグの原因ですからウォッチウィンドウを開いていつでもNoteの値が見られるようにしておきましょう。ウォッチウィンドウを開くには[デバッグ]メニューの[ウィンドウ] - [ウォッチ]を選択します。Noteの値が見られるようにするにはプログラム中で「Note」の部分をマウスで選択してウォッチウィンドウまでドロップします。
ウォッチウィンドウに直接「Note」と入力しても構いません。
このまま[F10](または[F8])を押しながらステップ実行して行ってNoteの値が変になる瞬間を捕まえましょう。ちょっとやってみてください。
どうですか?どこから変になっているかわかりましたか?実は3音目を鳴らす時点では既に変になっているのですよ。
■画像6
この段階で変数Noteの値が"F+"になっていますよね。つまり「ファの♯」です。けれど3音目は本当は「F+8」になっていなければなりません。ということは3音目の音の長さを読み込んでいるLength = Chr(Reader.Peek)の部分が悪いのでしょうか?
原因を突き止められなかった方は3音目(つまり3週目のループ)のLength = Chr(Reader.Peek)に着目しながらもう一度最初からステップ実行してみてください。
どうですか?今度はわかりましたか?3音目(つまり3週目のループ)のLength = Chr(Reader.Peek)を実行すると変数Lengthには"+"という値が入りますよね。でも、ここは「+」じゃなく「8」が読み込まれるはずの場所です。
はじめですからそろそろ種明かしをしましょう。読み込みに使用しているStringReaderクラスのPeekメソッドは次の文字を取得するメソッドです。似たような機能としてReadメソッドがあって、このReadメソッドを使うと次の文字を取得しながら1文字分読み進みます。ReadとPeekの違いは読み進むか読み進まないかです。Readで読んでいくと次の文字、また次の文字とどんどん読み進んでいくのにPeekで読んでいると永遠に同じ文字を読み続けます。Peekでは先に進めないのです。
つまり先に進むべきところで先に進んでいない個所があるのです。
補足 -
でもそれ、一人でやってたら気が付かないのでは? 今、途中まではみなさんにステップ実行をしながらデバッグをやってもらいましたが結局のところ私が間違っている個所を指摘してしまいました。もし、一人でやっていたらどうやって間違っている個所に気が付けばよいのでしょうか? 実のところデバッグの方法さえわかっていれば経験上誰でも97%間違っている個所にたどり着くことができます。1行ずつステップ実行をしていきながら変数の値やクラスのプロパティの値を確認し、値が予想通りか予想と異なるかを見ていくだけです。値の予想もできない人は努力が足りません。たいていは技術力ではなく「努力」の問題です。 途中までは変数やプロパティの値は予想通りで、あるところから予想と異なるようになるはずです。その「あるところ」こそ原因の可能性が高い行です。仮にその行に原因がなくてもその行 をたどって原因にたどり着けます。 行が特定できたらその行でしようしているメソッドやキーワードをよく調べて動作を研究することになります。 |
では、Peekを使っている個所に注目しましょう。
実はSign = Chr(Reader.Peek)の行で「+」を読み込んだ後で1文字分読み進めていないのが原因です。ですから、次にまた読み込んだときに同じ「+」記号をもう一度読み込んでしまい。そこから文字がずれて変になってしまっているのです。
ここは「+」記号を読み込んだ後は1文字読み進める処理を追加しましょう。
コードの部分だけ掲載しますがどの部分だかわかりますよね。コメントで示してある行を追加してください。
'▼次の文字が-+(調号)の場合は"C+",
"B-"のように音階と一緒にする。 Sign = Chr(Reader.Peek) If Sign = "+" OrElse Sign = "-" Then Note &= Sign Reader.Read() '←この行を追加する End If |
■リスト4
これで一つ目のバグが修正できました。お疲れ様です。
1つ目のバグを修正したところで実行してみるとまた同じところでエラーになります。エラーで止まったところで変数Noteの値を確認してみると今度は"8"になっています。
さきほどのエラーの他にもエラーがあるのです。さぁステップ実行してエラーの個所を確認してみてください。どこが変なのでしょうか?
いかがでしょうか?確認できましたか?すぐに答えを見ないで自分の力で頑張ってみてください。あなたの力でバグを発見して取り除くことができるはずです(多分)。
では解答編です。ステップ実行すると3音目は"F+8"と正しく読めているのがわかると思います。ところが4音目を読むときに"8"という数字を読み込んでしまいます。4音目の始めは"G"のはすです。またどこかで読み込み位置がずれてしまっているのです。
今回は音符の長さを読み込んでいるところに問題があるのです。問題と言っても先ほどと同じです。Peekで読み込んでいるところでReadを使って1文字進める必要があるのです。次のコードでコメントで示してある部分を追加してください。
'▼次の文字が数字の場合は"C8","F+8"のように音階と一緒にする。 Length = Chr(Reader.Peek) If IsNumeric(Length) Then Note &= Length Reader.Read() '←この行を追加する
'▼その次の文字も数字の場合も同様。たとえば"D+16"などの場合 End If |
■リスト5
ついでに他にもPeekを使っている個所がないか今のうちにチェックしておきましょう。と言ってもDo Whileのところ以外はもうないということが確認できるだけです。
この段階で演奏してみるとなんとなくメロディーがわかるくらいの長さになります。でもやはり途中でエラーになります。「♪おーおきなのっぽのふる」の次でエラーになるようです。
エラー個所はまたしてもPlayStringメソッドです。そしてNoteの値を確認してみると今度は"<"となっています。
また、"<"と">"を読み込むところではPeekは使われていませんから今までの修正方法では通用しません。
今回はバグの原因を突き止める方法を説明するのが主目的ですから、ここは軽く流してしまいますが、">"と"<"は音符ではないので演奏しないようにすればよいだけです。Player.PlayString(Note, Octarb)をIf文でくくって次のようにすれば解決です。
'▼読み込んだ音を発音する。 If Note <> "<" AndAlso Note <> ">" Then '←この行を追加する Player.PlayString(Note, Octarb) End If '←この行を追加する |
■リスト6
この段階で演奏してみると大分メロディーがはっきりします。まぎれもなく「大きな古時計」の最初のフレーズです。
そして無事に演奏が終了したと思った瞬間に次のエラーが発生します。
今度もPlayStringメソッドでのエラーです。ここまでくればもうコツはつかんだと思います。まず、やるべきことは引数の確認ですね。第1引数である変数Noteの値を確認してみると今度は"・"という値になっているのが確認できます。
楽譜文字列にはこのような文字はないのに妙ですね。どこからこの文字がでてきているのでしょうか?
今度もステップ実行で根気良く確認していけばわかるかもしれませんが、最後まで演奏しないとエラーにならないのでちょっと面倒です。そこで、最後の音符を演奏したすぐ次で実行が一時停止するように仕組んで様子を見てみましょう。
次のようにPlayStringメソッドの下にIf文とStopステートメントを追加してください。
'▼読み込んだ音を発音する。 If Note <> "<" AndAlso Note <> ">" Then Player.PlayString(Note, Octarb)
If
Note = "G2"
Then End If |
■リスト7
VB2005 Express Edition以外を使用している場合は条件付ブレークポイントを使ってもっと簡単に止めることができますが、今回はおなじコードを使って動作を確認することにします。
参考 - 条件付きブレークポイント VB2005のExpress Edition以外ではブレークポイントに条件を付けられるのでStopを使用する必要はありません。 VB2005でブレークポイントに条件をつけるには、ブレークポイントを示す茶色い丸の上で右クリックをして「条件」を選択します。そうするとウィンドウが表示されるので条件欄にたとえば、「Note="G2"」のように入力します。 VB.NET2002, VB.NET2003では、同じく茶色い丸の上で右クリックをして「ブレークポイントのプロパティ」を選択します。そうするとウィンドウが表示されるので「条件」ボタンを押してから、条件欄に「Note="G2"」のように入力します。 |
実行してStopで停止するまで大きな古時計を聞いていて下さい。
Stopで一時停止したら例のよってステップ実行でひとつずつ実行して行きましょう。すぐにおかしい点に気が付くはずです。
もう音符が最後なのにもう1周ループをまわろうとしているのです。そして、次の文字を読み込むとさきほどの「・」という文字になります。
ループの条件はWhile Reader.Peekですね。答えを先に言うとこの条件が間違っているのです。デバッグで確認するにはReader.Peekがどのような値を返しているのか見れば良いでしょう。
ウォッチウィンドウを使っても見ることはできますがここはイミディエイトウィンドウを使用しましょう。基本的にメソッドの戻り値を確認するにはウォッチウィンドウではなくイミディエイトウィンドウを使う方が良いです。ウォッチウィンドウでメソッドの戻り値を確認するということは用がないときでも何回もウォッチウィンドウによってメソッドが呼び出されることを意味しています。
これでは時間的にロスが発生する場合もありますし、何回も実行したくないメソッドもあります。
イミディエイトウィンドウを表示するには[Ctrl] + [G]が最も簡単です。[デバッグ]メニューの[ウィンドウ] - [イミディエイト]で表示することもできます。
イミディエイトウィンドウが表示されたら一番下に次の通りに入力してください。
? Reader.Peek
[ENTER]を押すとPeekの戻り値が確認できます。この状態では-1と表示されるはずです。
Peekメソッドが-1を返すのがどういう場合か、MSDNライブラリを使うとすぐに調べられます。プログラムのところでPeekのところにカーソル(マウスカーソルではなく文字を入力するカーソル)を持っていって[F1]を押すと情報が表示されます。(適切なMSDNライブラリが指定されていない場合は表示されません。)
その説明によると、使用できる文字がない場合は-1を返すようなことが書いてあります。つまり-1が返ってきたらもう終わりと言うことですね。ですから終了条件は「-1が返ってくるまで」と記述すべきということがわかります。
早速修正して次のようにしてください。
Do Until Reader.Peek = -1 '楽譜の最後に到達するまでループを実行 |
■リスト8
これでもう一度実行してみましょう。
実行すると…ひょっとしてStopで止まってしまいましたか?さきほどのStopはもういらないのでIf文とともに削除して置いてください。
この状態で実行すると今度は最後まで演奏できてエラーも発生しません。
おめでとうございます!これですべてのバグが取り除かれました。デバッグはここに完了したわけです。
完了版のコードを載せておきます。
Private
Sub Button1_Click(ByVal
sender As System.Object,
ByVal e As System.EventArgs)
Handles Button1.Click
Dim
CmdSound As String
'楽譜を表す文字列 '●前処理 '楽器・楽譜の初期設定 '音色にシンセリードを指定。
'このコメントをはずすとMIDIデバイスがあってもビープ音による演奏が行われる。
'大きな古時計の楽譜(最初のフレーズのみ) '●メイン処理 '楽譜から一音ずつ取り出して演奏する Reader = New IO.StringReader(CmdSound) Do Until Reader.Peek = -1 '楽譜の最後に到達するまでループを実行 Note = Chr(Reader.Read) '楽譜の次の音階を読む。
'▼読み込んだのが音階ではなくオクターブを変える記号の場合
'▼次の文字が-+(調号)の場合は"C+", "B-"のように音階と一緒にする。
'▼次の文字が数字の場合は"C8","F+8"のように音階と一緒にする。
'▼その次の文字も数字の場合も同様。たとえば"D+16"などの場合 End If
'▼読み込んだ音を発音する。 Loop '●後処理 Reader.Close() End Sub |
■リスト9
以上、おそまつでしたがデバッグの実際の手順でした。ステップ実行やウォッチ、イミディエイトウィンドウの使いどころなど理解していただけたでしょうか?
その前にいきなり妙なプログラムをデバッグすることになって何がなんだかわからなかった方もいらっしゃるかもしれませんね。
とは言え、他人が書いたプログラムをデバッグできるようになるということはVBのスキル面では重要なことです。
今回はよくわからなかった方もいらっしゃるかもしれませんが、今回学んだデバッグの方法を使ってあなたのプログラムをより完成度の高いものとしてください。それに、どこにバグがあるのか素早く発見できるようになっていく道筋くらいはつかんでいただけたのではないでしょうか?
最後に興味のある方のためにSound.dllのソースコードをダウンロードできるようにしておきます。VB.NET2003のものを置いておきますがVB2005でも開けます。
ソースコードは個人的な目的以外では使用しないで下さい。
SoundSource2003.lzh (7KB)
このSound.dllは今回のデバッグの説明のために作成したものです。つまらないプログラムをデバッグするよりは完成したらメロディーが流れるプログラムなんて素敵だなっと思いました。でも、デバッグを説明する前にSound.dllのことを説明しなければならなくなったのでちょっとまずかったかなとも思っています。
Sound.dllを使うと簡単にメロディーが流せます。マルチスレッドを使えば非同期再生もできます。ただ、和音が鳴らせなかったりテンポが変更できなかったり、付点が表現できなかったりいろいろと困った点があり実用には向きません。
でも、MIDIがなくてもビープでメロディーが鳴らせるというのはちょっと面白くないですか?
大分昔の話なんですけど、私のパソコンには音源がはいっていなかったのに、イースをやったらちゃんとBGMが鳴ったんでとても驚いたことがあります。ビープ音で奏でていたとしか思えません。まったく日本ファルコムはすごいです。