【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ステートメントと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時代の資料ですが、今でも十分使えます。特にKB146864はVBAで例外処理をする上で必読とも言える内容がいっぱい書いてあるので一度目を通しておきましょう。
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も出来ますし、エラーの内容で分岐だって出来ます。やりましょう。やってください。もうクソコードを読みたくないです。