読者です 読者をやめる 読者になる 読者になる

【VB】VB 6.0の例外処理

もう2013年も残り3ヶ月を切ったと言うのに未だにVBA(VB6.0)なんて過去の遺産を保守しなきゃいけない現状そのものがもうイライラするんだけど、動いちゃってるものは仕方ないのでやるしかない。

そんなわけでどこかの誰かが片手間で作ったようなコードを読む機会が最近増えているんだけど、もうね、なんと言うかね、お前らもうちょっとマシなコードを書けと。もっとマシなコメントを書けと。変数全部にコメントなんかいらねぇ、ある程度変数名でわかるようにしろ。Option Explicitをつけろバカタレ。定数にしか使ってないならConstつけとけ。

色んなイライラ要素があるんですが、特に例外処理がイケてないことが非常に多い。酷い時には一つもない。DBに接続するような処理ですら例外処理が入ってない。

まぁ確かに、VB6.0にはtry catchなんてものはないんですが、例外処理そのものがないわけではなく、GoTo文になってしまうだけでほぼ同じことが出来ます。GoTo文と言うと条件反射で眉をひそめる人が多いんですが、あらゆる糖衣構文はGoToをラップしただけです。構文がないのが悪い。なければ作る。GoToで。

ぐぐったらすぐ出てきそうな入門サイトにはOn Error Resume Nextしか書いてなかったりするし、多少詳しく書いてあるサイトでも抜け落ちている説明があったりするので、ちゃんとまとめておきましょう。

On Errorステートメント

On Errorステートメントを記述することで、実行時エラーをトラップすることが出来ます。実行時エラーと言うのは、ものすごくざっくり説明するとコンパイルエラーやシンタックスエラー以外のエラーです。try catchでもそんなのトラップできないでしょ?逆に言えば、try catchでトラップできそうなエラーは大体実行時エラーです。

On Errorステートメントには三つの記述方法があります。

ステートメントの説明は以下の通り。(Excelのヘルプより引用)

On Error GoTo line
引数 line に指定した行から始まるエラー処理ルーチンを有効にします。引数 line は必ず指定します。引数 line には任意の行ラベルまたは行番号を指定します。実行時エラーが生成されると、ここで設定したエラー処理ルーチンにプログラムの制御が移り、エラー処理ルーチンがアクティブになります。引数 line に指定する行は、On Error ステートメントと同じプロシージャ内に存在しなければなりません。この制限に従わなければ、コンパイル時エラーが発生します。
On Error Resume Next
実行時エラーが発生してもプログラムを中断せず、エラーが発生したステートメントの次のステートメントから実行を継続します。オブジェクトを操作する場合は、On Error GoTo ステートメントではなく、このステートメントを使ってください。
On Error GoTo 0
現在のプロシージャに含まれる使用可能なエラー処理ルーチンを無効にします。

On Error GoTo 0

これが呼ばれた瞬間にtryの部分が終わると思えばそれでいいです。

構造化っぽい要素を最低限保つために、また保守や仕様変更のことも考えてOn Errorステートメントを使ったらマナーとしてこいつをどっかに入れておきましょう。

On Error GoTo line

VB6.0におけるtry catch。lineには行番号かラベルを指定することが出来ますが、行番号を指定することはまずない…って言うか、誰も読めないのでやめてください。

たまたま前回作ったjsonのパーサに使ってる部分があるので引用してみましょう。

Public Function Parse(ByVal strJson As String) As Object
    
    Dim json As Object
    
    On Error GoTo ParseError
    Set json = m_js.codeobject.jsonParse(strJson)
    On Error GoTo 0
    
    '省略

    Exit Function
    
ParseError:
    
    Debug.Print Err.Description
    Set Parse = Nothing
    
End Function

Set jsonのところでパースに失敗、ないしはObject以外のものが返ってきてしまったらParseErrorラベルに飛び、エラー用の対応をしています。

よく忘れるんですが、あくまでラベルなので、直前にExit Function/Subされていないとエラーがトラップされていなくてもそこに突っ込んでいきます。特にFunctionなんかだと何度やってもエラー時の値しか返ってこなくなったりしてよくわからなくなります。気をつけましょう。

On Error Resume Next

エラーが起きても別にどっちでもいいような処理にはOn Error Resume Nextを使います。エラーを握りつぶしてさっさと次の行の処理を行います。つまり、滅多なことでは使うなってことです。

これもOn Error GoTo 0が呼ばれるかプロシージャが終わるまでは適用されるので、適用したい範囲が終わったらしっかりOn Error GoTo 0を呼び出してあげましょう。

Resumeステートメント

catchの終わり、程度に考えれば大体あってます。catchして処理を終えた後、どこから処理を再開するかを指定出来ます。

ResumeステートメントにはOn Errorステートメントと同様に三種類あります。

  • Resume line
  • Resume Next
  • Resume [0]

ステートメントの説明は以下の通り。(Excelのヘルプより引用)

Resume line
引数 line に指定した行からプログラムの実行が再開されます。引数 line は必ず指定します。引数 line には行ラベルまたは行番号を指定します。また、エラー処理ルーチンと同じプロシージャに指定する必要があります。
Resume Next
エラー処理ルーチンと同じプロシージャ内でエラーが発生した場合、エラーの原因となったステートメントの次のステートメントからプログラムの実行が再開されます。呼び出されたプロシージャ内でエラーが発生した場合、エラー処理ルーチンを含むプロシージャが最後に呼び出したステートメントの次のステートメント、または On Error Resume Next ステートメントからプログラムの実行が再開されます。
Resume [0]
エラー処理ルーチンと同じプロシージャ内でエラーが発生した場合、エラーの原因となったステートメントからプログラムの実行が再開されます。呼び出されたプロシージャ内でエラーが発生した場合、エラー処理ルーチンを含むプロシージャが最後に呼び出したステートメントからプログラムの実行が再開されます。

要はOn Errorステートメントと大体一緒ってことです。

ループ中のOn ErrorステートメントとResumeステートメント

例えばこんなコードがあったとします。

Public Sub ResumeTest()
    Dim i As Long: i = 0
    Dim hoge As Integer: hoge = 0
    
    For i To 10
        On Error Goto ErrHandle
        hoge = 1 / 0
        On Error Goto 0
Continue:
    Next

    Exit Sub
    
ErrHandle:
    
    Debug.Print Err.Description
    GoTo Continue
    
End Sub

VBAにはcontinueもないのでよくGoToで実装したりするんですが、このコードだとループの二回目でエラーがトラップできません。

ContinueをOn Error Goto 0の前にしてもダメです。Resumeを使ってあげましょう。

Public Sub ResumeTest()
    Dim i As Long: i = 0
    Dim hoge As Integer: hoge = 0
    
    For i To 10
        On Error Goto ErrHandle
        hoge = 1 / 0
        On Error Goto 0
Continue:
    Next

    Exit Sub
    
ErrHandle:
    
    Debug.Print Err.Description
    Resume Continue
    
End Sub

Errオブジェクト

実行時エラーが発生した時点で自動でErrオブジェクトが生成されます。エラーの原因で分岐させたり、スタックトレースライクな何かを取得するにはこいつを使います。

エラーの分岐に使うのはNumberプロパティデバッグに使ったりメッセージボックスなんかに表示させたい場合はDescriptionプロパティスタックトレースっぽいものはSourceプロパティErlプロパティあたりをよく使います。NumberとDescriptionはともかく、SourceとErlはあまり期待しないほうがいいです。特にソース。プロシージャレベルではとれないのであんまり意味ないです。

どんなNumberが返ってくるかはKB146864のErr関数の項目に記載されています。エラーの具体例はKB142138にあります。

どちらもExcel 97時代の資料ですが、今でも十分使えます。特にKB146864VBAで例外処理をする上で必読とも言える内容がいっぱい書いてあるので一度目を通しておきましょう。

Raiseメソッド

ErrオブジェクトのRaiseメソッドを使用することでthrowも可能です。

Public Function Parse(ByVal strJson As String) As Object
    
    Dim json As Object
    
    On Error GoTo ParseError
    Set json = m_js.codeobject.jsonParse(strJson)
    On Error GoTo 0
    
    '省略

    Exit Function
    
ParseError:
    
    Call Err.Raise(Err.Number)
    
End Function

勿論呼び出し元でOn Errorステートメントがあればそっちでトラップしてくれます。

Public Sub ParentSub()

    On Error GoTo fuga
    Call Child
    On Error GoTo 0
    
    Exit Sub
fuga:
    Debug.Print Err.Description

End Sub

Public Sub Child()
    Dim a As Integer
    On Error GoTo hoge
    a = 1 / 0
    On Error GoTo 0
    
    Exit Sub
hoge:
    Call Err.Raise(Err.Number)
End Sub

vbObjectErrorを使用することでアプリケーション特有のエラーを発生させることも出来ます。共通変数をまとめたモジュールに定数として突っ込んでおき、ついでにDescriptionを返すFunctionを作っておくと便利です。

Public Sub ParentSub()

    On Error GoTo fuga
    Call Child
    On Error GoTo 0
    
    Exit Sub
fuga:
    
    If Err.Number = vbObjectError + 1000 Then
        Debug.Print Err.Number
        Debug.Print Err.Description
    End If
End Sub

Public Sub Child()
    Dim a As Integer
    On Error GoTo hoge
    a = 1 / 0
    On Error GoTo 0
    
    Exit Sub
hoge:
    Call Err.Raise(vbObjectError + 1000, Description:="My Error")
End Sub

まとめ

とまぁ、スマートではないにしろ色々な方法でtryもcatchもfinallyもthrowも出来ますし、エラーの内容で分岐だって出来ます。やりましょう。やってください。もうクソコードを読みたくないです。