Small Basic: デバッグ方法 (ja-JP)
この記事では Small Basic 言語で書かれたプログラムの以下のようなデバッグ手段を紹介します。また、発行されたプログラムについてデバッグの実例を示します。
- TextWindow.WriteLine() で変数の内容を表示する
- "If debug Then..." を使いデバッグ用のコードを書く
- サブルーチンのためのテストプログラムを書く
- バグの一覧表を作る
- エラーメッセージの意味を知る
- 「昇格する」と Visual Studio デバッガを使う
TextWindow.WriteLine() で変数の内容を表示する
もしあなたのプログラムにおかしなところを見つけたとき、どのようにして探しますか。Small Basic IDE (統合開発環境)には変数をウォッチ(監視)するような機能はありません。しかし、変数に期待しない値が入ってしまうことがよく起こります。 それを見つけるには TextWindow.Write() または TextWindow.WriteLine() をデバッグのために追加し、変数の内容を見てみるとよいでしょう。
配列 arry の内容を見るには、単純に TextWindow.WriteLine(arry) を使用するか、以下のようなコードを書きます。
TextWindow.ForegroundColor = "Yellow"
num = Array.GetItemCount(arry)
index = Array.GetAllIndices(arry)
For i = 1 To num
TextWindow.WriteLine("arry[" + index[i] + "]=" + arry[index[i]])
EndFor
TextWindow.ForegroundColor = "Gray"
テキストベースのプログラムのデバッグの場合、前景色 (foreground color) を変更することにより、プログラムの本来の出力とデバッグのためのメッセージを区別しやすくできます。
"If debug Then..." を使いデバッグのコードを書く
配列の内容を表示するようなデバッグのためのサブルーチンを書いた場合、それは将来も役に立つ可能性があります。しかし、これらのコードが必要なのはデバッグ中だけです。そこで、debug フラグを使ってデバッグルーチンをオン・オフします。
Maze 0.4 (PNC833-0) は debug フラグを使ったひとつのサンプルです。以下の行で debug フラグをオフにしています。debug = "True" によってフラグをオンにします。
27.debug = "False"
以下の 4 行は配列 "cell" に作られた迷路をテキストとして表示します。
61.If debug Then
62. DumpMaze()
63. TextWindow.WriteLine(title)
64.EndIf
以下のコードは迷路の生成をゆっくりさせて見えるようにし、変数を表示したあと、TextWindow.Read() を使ってポーズ(一時停止)しています。
229. If debug Then
230. Program.Delay(20)
231. EndIf
232. ' 2. Add the neighboring walls of the cell to the wall list.
234. AddWallToList()
235. If debug Then
236. TextWindow.WriteLine("iWalls=" + iWalls)
237. TextWindow.Write("nWalls=" + nWalls)
238. TextWindow.Read()
239. EndIf
そしてデバッグが終わったら、これらの行は(Small Basic IDE の [Ctrl]+F で)簡単に見つけ削除することができます。実際にこれらのルーチンは Maze 1.2 (PNC833-12) では削除されています。
サブルーチンのためのテストプログラムを書く
汎用的な(応用範囲の広い)サブルーチンを書くことは生産性の向上に貢献します。筆者の場合、色、計算、マウスなどのサブルーチンを多くのプログラムで再利用しています。ただし汎用サブルーチンにはバグがないよう、よくテストしなければなりません。バグが無いこと(少ないこと)が生産性の前提条件ですから。
サブルーチンをテストするためには、サブルーチンのためのテストプログラムを書くのが合理的です。よいテストプログラムはバグを見つけるだけでなく、デバッグ後のリグレッション(品質低下)のチェックをも簡単にしてくれます。
大きな数 n の組み合わせ nCr を求めるプログラム (CPQ608) には、TestDiv() というルーチンがあり、汎用的なサブルーチン Div() をテストします。
以下は TestDiv() で見つけた Div() に問題を起こすパラメタです。
'a = "434399216531770650390143258708"
'b = "6752306690329"
'a = "397896921587794748049229269710"
'b = "8642083658481"
バグの一覧表を作る
以下のリストは Shapes 1.1 (TLW744) のデバッグ中に書いたものです。たくさんのコードを一度に書くと、プログラムがたくさんのおかしな振る舞いを起こすことがあります。こういうときは、それらの現象をひとつひとつ書き留めてリストを作るとよいでしょう。なぜならそれらの現象にはたくさんの原因があることが多いからです。さらにデバッグ後には、このリストがプログラムのためのよいテストセットになります。
- 図形リサイズ時の CalcOpposite() の結果がおかしい
原因:変数 func がこのルーチンのために設定されていなかった - ピンチ(ちいさな円)が図形外をクリックしても消えない
原因:処理がまだなかった - 図形移動時の枠が表示されないことがある
原因:変数 i に selectedshape が設定されていなかった - ピンチがカットアンドペースト時に残ることがある
原因:以下と同じ - コピーしてもピンチが再表示されない
原因:貼り付け時にピンチを表示していなかった - マウスボタンをリリースしても(放しても)図形が移動してしまう
原因:マウスハンドラの中でマウスリリースの情報を保持しておく必要があった - 図形の色を変更すると他の図形のサイズと位置が変わる
原因:不要な RegisterShapeData() が呼ばれ、変数 i が壊れていた - 楕円や三角を追加すると他の図形が選択される
原因:(メニュー項目をクリックしたときの)図形の追加をキャンセル処理が変数 obj を壊していた - 図形を移動できなくなることがある
原因:マウスリリースを表すフラグをクリアしていなかった
エラーメッセージの意味を知る
Small Basic で表示されるエラーメッセージには 2 種類あります。ひとつはコンパイルエラーです。もうひとつはランタイムエラーです。これらのメッセージの意味を知ると開発の助けになります。
コンパイルエラーは [実行] ボタンを押した後、プログラムに文法的なエラー(誤り)があると、ソースコードの下に表示されます。以下の図はプログラム Fifty (BRQ733) のコンパイルエラーを示しています。
いくつかのエラーが発見されました...
11,28: 変数 'files' は使われていますが、それに対する値は指定されていません。正しく値を指定したか確認してください。
29,24: 変数 'buf' は使われていますが、それに対する値は指定されていません。正しく値を指定したか確認してください。
先頭の数字はソースコードの問題の個所の行と桁を表していて、エラーメッセージをダブルクリックすることで、その場所へジャンプすることができます。これらのコンパイルエラーメッセージには、何が起きたかということと、それを修正するためのアドバイスが書かれています。上記のケースでは、File.GetFiles(path) と File.ReadContents(filename) のある行がSmall Basic サーバによって自動的にコメント化されているために、エラーになっています。コメントを元に戻して実行し直すとエラーはなくなります。
一方、ランタイムエラーはプログラム実行時に、実行を続けられない問題が発生したときに表示されます。以下の図はゼロ除算が起きたときの事例です。
Decimal 型の値が大きすぎるか、または小さすぎます。
場所 System.Decimal..ctor(Double value)
場所 Microsoft.SmallBasic.Library.Math.Remainder(Primitive dividend, Primitive divisor)
場所 _SmallBasicProgram._Main()
このテキストボックスに表示されたリストは「スタックトレース」と呼ばれるものです。このリストはエラーの時点でどのサブルーチンがどのサブルーチンを呼んでいるかを表しています。上記のケースでは、スタックトレースはプログラムのメインの部分から余りを求める Math.Remainder() が呼ばれてエラーが起きていることを示しています。実際、divisor (割る数)に 0 が与えられていたためにこのエラーが起きました。
「昇格する」と Visual Studio のデバッガを使う
たくさんのデバッグルーチンを書いたにもかかわらず、バグによっては複雑すぎて原因を見つけられないことがあります。最後の手段としては、「昇格する」ボタンを押して Small Basic プログラムを Visual Basic プログラムに変換してしまう方法があります。このことによって Visual Studio の強力なデバッガを使用して元の Small Basic プログラムにあったバグを探すことができます。
ステップ 1: まだ行っていない場合は、Visual Basic 2010 Express をインストールします。
ステップ 2: 「昇格する」ボタンを押し、Visual Basic プログラムに変換するためのフォルダ名を入力します。
ステップ 3: Visual Studio 変換ウィザードで [次へ] または [完了] ボタンを押します。プログラム XXX.sb (または XXX.smallbasic)は XXXModule.vb に変換されます。
ステップ 4: スコープエラーを回避するために、'For i = 1 To n' を 'For XXXModule.i = 1 To n' に書き換えます。
ステップ 5: 必要に応じてソース行をダブルクリックし、ブレークポイントを設定します。
ステップ 6: [デバッグ開始] ボタンまたは [F5] キーを押し、プログラムを起動します。
ステップ 7: もしプログラムがブレークポイントで停止したら、[ステップ イン (F8)] または [ステップ オーバー (Shift+F8)] を押して次へ進めます。
ステップ 8: 「自動変数」、「ローカル」または「ウォッチ」タブを見て、変数の値を確認します。
ステップ 9: もしバグを見つけたら [デバッグの停止] ボタンを押し、プログラムを修正しコメントを残します。
ステップ 10: ステップ 6 以降を繰り返します。
そして、バグの改修を確認できたら、Small Basic IDE に戻って Visual Studio で行った修正を行います。
Small Basic と Visual Basic では以下のような文法の違いがありますので、適宜書き換えを行ってください。
- 配列:2 次元配列の記法は Small Basic では arry[i][j] ですが、Visual Basic では arry(i)(j) になります。また、配列の次元をarry(20)などのように宣言する必要があります。Small Basic 配列 1 オリジンですが Visual Basic 配列は 0 オリジンであることに気をつけてください。例えば配列のインデックスに 1から19を使用する場合、(インデックス 0 を含む) 20 エントリを確保する必要があります。
- 変数の型:Small Basic の変数の型は Primitive のみで、'If ans = CType("", Primitive) Then' のような型変換が必要な場合があります。
- ブール型:型 Primitive は以下のようにブール型として利用できます。
clicked = true ' or false
If clicked Then
If clicked = CType(false, Primitive) Then - 配列:Visual Basic では(型 Primitive の)2次元配列への代入ができません。'shape(5)("angle") = 90' は次のように書き換えます。
saved = shape(5)
saved("angle") = 90
shape(5) = saved - For ブロック変数:For 制御変数は For ブロックのスコープにバインドされて推論ができないという警告が出ます。これを回避するには、制御変数の前にモジュール名を明示します。ブロックの中の変更は不要です。
修正前:For i = 1 To 10
修正後:For FooModule.i = 1 To 10
まとめ
これらの方法は、ここ 2 年の Small Basic プログラミングの成果ですが、全てというわけでありません。プログラミングやデバッグには創造性が大切です。もしもあなたがよい方法を発見したら、是非その方法をこの記事に追加してください。Small Basic はとてもコンパクトでプログラミングを学ぶのにとても適した言語です。しかも簡単で強力です。