t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBA エラーは忌むべきものではなく、非常に重要な機能である

プログラマーなら誰でもエラーで悩んだ経験があるだろう。特に初心者の頃はなかなか思ったように動かずにイライラしたことがあるハズだ。

しかしエラーは本来、忌むべきものではない。
プログラムエラーは現実世界で問題が起きる前に、「それは間違っている」と知らせてくれる大変ありがたい機能だ。

エラーがなければプログラマーは、いとも簡単にバグを混入させてしまう。
たとえばプログラムのミスで振り込んだはずのお金が送金されなかったら。または飛行機の制御プログラムにバグがあり墜落を引き起こしたら。
このようにプログラムのバグで現実の問題を引き起こすというのは大変恐ろしい。それだったら最初から動かないほうがマシだ。

まぁVBAでそんな大それたプログラムを作ることはないとしても、バグのあるマクロを配布したせいで信頼を損ねるといったことは十分にありえる。

バグを回避するためには、間違ったコードがエラーになるようにコーディングすれば良い。そうすれば少なくともバグに気づくことはできる。

たとえば変数宣言を正しい型で行うこともバグの可視化につながる。

では、変数の型がバグの発見に有益である例を示す。
以下のプログラムで、それぞれのInputBoxに1を入力してみよう。

Sub hoge()
    a = InputBox("整数を入力してください。")
    b = InputBox("整数を入力してください。")
    MsgBox a & " + " & b & " = " & a + b
End Sub

これは足し算ではなく、文字列として結合されてしまい、結果は"1 + 1 = 11"となる。

VBAでは文字列と文字列をプラスで結合することができるため、プログラミングの文法上は間違っていない。しかし作者が意図した動作とは異なるため、これはバグであるといえる。

このようなバグを防ぐには、変数を正しい型で宣言しておけばよい。
以下のように、変数をLong型で宣言しておけば、代入した時点でLong型になる。
マクロを実行し、InputBoxに整数と見なせない文字列("aaa"など)を入力すると、その時点でエラーが発生する。

Sub hoge()
    Dim a As Long, b As Long
    a = InputBox("整数を入力してください。")
    b = InputBox("整数を入力してください。")
    MsgBox a & " + " & b & " = " & a + b
End Sub

変数の型を正しく宣言しておくことで、バグを未然に防ぐことができた。

次にもう一つ別の観点から変数宣言の有用性を説明するため、掛け算でも試してみる。最初は変数宣言無しで試す。

それぞれ2と3を入力すると、正しく計算できる。

Sub hoge()
    a = InputBox("整数を入力してください。")
    b = InputBox("整数を入力してください。")
    MsgBox a & " * " & b & " = " & a * b
End Sub

文字列同士を掛け算しようとすると、VBAではそれを数値と解釈する。
このため、足し算のときと異なり、正しい結果が返される。

次に、上記のプログラムに文字列 "aaa" と "bbb"を与えてみる。
すると、掛け算した時点で「型が一致しない」というエラーになる。

エラーになった原因は、変数a、bに文字列を代入したことだ。
しかしエラーが出た個所は、掛け算した時点である。
つまり原因と、エラー箇所が離れている。

単純なプログラムならすぐに原因に気付くが、大きなプログラムでは原因とエラー箇所が離れていると気付きにくい。

これも、変数を正しい型で宣言しておくことで防ぐことができる。
以下のマクロでInputBoxにそれぞれ"aaa"、"bbb"を与えてみよう。

Sub hoge()
    Dim a As Long, b As Long
    a = InputBox("整数を入力してください。")
    b = InputBox("整数を入力してください。")
    MsgBox a & " * " & b & " = " & a * b
End Sub

するとやはり型の不一致エラーが発生するが、今度はaに文字列"aaa"を代入した時点でエラーが出ている。

これで原因とエラー箇所が一致するので、プログラムが大きくなってもバグの原因究明は簡単になる。

次に、キャストについて説明する。
キャストとはある型のデータが別の型にが変換されることを指す用語である。
以下のように、Long型の変数に文字列の"10"を代入すると、自動的にLong型の数値 10 に変換される。

Dim a As Long
a = "10"

このように自動的に型変換されることを、暗黙のキャストと呼ぶ。

それと反対に、明示的にキャストするには、キャスト関数を使用する。
たとえばLong型にキャストするにはCLng関数を用いる。

Dim a As Long
a = CLng("10")

結果は変わらないが、コード上で明示的にLong型にキャストしているので、「ここでキャストされる」というのが分かりやすい。

キャスト処理も、バグを引き起こしやすい。
以下のプログラムは結果が4になる。

Dim a As Long
Dim b As Long
a = "1.5"
b = "2"
MsgBox a * b

文字列 "1.5" をLong型に代入しようとすると、一旦実数 1.5 と評価され、それから四捨五入されて整数 2 になる。

たとえば整数の入力を求めるInputBoxで実数が入力された場合、直接Long型に代入してしまうと自動的に四捨五入されて整数になるが、エラーが出ない以上、プログラムの利用者は入力値に基づき、正しい計算結果が得られたと勘違いする可能性が高い。

これは実務上の問題を引き起こすので、大変危険である。
整数しか入力できないケースであれば、実数が入力された際にエラーメッセージで利用者に入力の間違いを知らせるべきである。

このためには、一旦InputBoxの戻り値を文字列型として受け取り、検査のうえ有効な整数であると認められた場合のみ処理を続行するようにプログラムする。

Sub hoge()
    Dim a As Long
    Dim s As String
    s = InputBox("整数を入力してください。")
    If IsNumeric(s) Then
        If CLng(s) = CDbl(s) Then
            '入力値を実数と整数にキャストして比較したとき、差が出なければ、実質整数であるとみなす。
            'CLngの他に、Round関数やInt関数で整数化する方法もある。
            'ただし、"1.0"などの入力も禁止したい場合は別の手法で検査する必要がある。
            '(文字列としてドットが含まれるかを精査するなど)
            a = CLng(s)
        Else
            MsgBox "少数は入力できません。" & vbNewLine & "処理を中断します。", vbExclamation, "エラー"
            Exit Sub
        End If
    Else
        '数値として認識できない文字列などが入力された場合。
        MsgBox "整数以外は入力できません。" & vbNewLine & "処理を中断します。", vbExclamation, "エラー"
        Exit Sub
    End If
    
    MsgBox "整数が入力されました。" & vbNewLine & "値:" & CStr(a), vbInformation, "成功"
End Sub

プログラム上エラーにならないものでも実務上問題を引き起こすものは、適切にエラーメッセージを表示させて修正を促す必要がある。今までエラーメッセージに注意されていたのが、今度はエラーメッセージを設計する側に回るわけだ。

安全なプログラムというのは実務上の問題が発生しないように様々に工夫が凝らされている。
ネットや書籍のサンプルは複雑さを排除するため安全性を考慮していないことが多いので注意が必要である。

とりわけ金額や時間などの重要な数字を扱う処理では、あらかじめどんな間違いが発生するか想像し、慎重にエラーを設計したい。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。