一般的にエラー処理は、各関数では最低限行い、後は呼び出し元に Throw するのが一般的かと思いますが、VBA ではそれを簡単には実装できません。
エラーハンドラを作ると、そのプロシージャ内ではエラーが処理済みになり、呼び出し元には伝わりません。後処理をしたうえで呼び出し元にもエラーを伝えるには、ハンドラ内で Err.Raise により改めてエラーを発生させる必要があります。
具体的な例を挙げると、
- Open 関数でファイルを開いた後、エラーになった場合でも Close は発生させたい。
- その上で、エラーを呼び出し元に伝達したい。
といった場合です。
errNumber = Err.Number
errDescription = Err.Description
On Error GoTo 0
Call Err.Raise(errNumber, , errDescription)
解決には上記のようなコードを使います。解説とサンプルを紹介致します。
On Error GoTo 0 とエラー情報の退避
On Error GoTo 0 は、そのプロシージャで有効な On Error の設定を無効にします。ハンドラの中で使うと、以降に発生したエラーはこのプロシージャでは処理されず、呼び出し元へ伝わります。
なお、エラーハンドラの実行中に Err.Raise で新たなエラーを発生させた場合も、そのエラーは同じハンドラには戻らず呼び出し元に渡されます。ここで On Error GoTo 0 を入れる意義は、「これ以降はこのプロシージャで処理しない」ことをコード上で明示する点にあります。
また、Err オブジェクトの内容は後続の処理(On Error ステートメント(On Error GoTo 0 を含む)、Resume、Exit Sub など)で上書き・クリアされることがあります。そのため、ハンドラの最初に Err.Number と Err.Description を変数へ退避しておくのが安全です。Err.Raise の引数に説明文を渡しておけば、呼び出し元でも元のエラー内容を確認できます。
処理の流れは次のとおりです。
- エラーが発生し、エラーハンドラに飛ぶ
- エラー情報を変数に退避する
On Error GoTo 0でエラーハンドラを解除する- 後処理(Close など)を行う
Err.Raiseでエラーを呼び出し元に伝える
これにより、他のプログラミング言語で言うところの、catch の中で throw するような Try-Catch-Finally を再現できます。
コードサンプル
エラー処理 → Throw だけでいい場合
Public sub Caller()
On Error GoTo ERROR_HANDLER
Call Called
Exit Sub
ERROR_HANDLER:
Debug.Print(Err.Number)
End Sub
Public sub Called()
On Error GoTo ERROR_HANDLER
Dim errNumber as Long
' エラーの起こり得る処理を書く
Exit Sub
ERROR_HANDLER:
errNumber = Err.Number
On Error GoTo 0
' ここにエラー発生後の処理を書く
' 呼び出し元にエラーを伝達します
Call Err.Raise(errNumber)
End Sub
Try-Catch-Finally を再現する
結構長い記述が必要です。
Public sub Caller()
On Error GoTo ERROR_HANDLER
Call Called
Exit Sub
ERROR_HANDLER:
Debug.Print(Err.Number)
End Sub
Public sub Called()
On Error GoTo ERROR_HANDLER
Dim errNumber as Long
' エラーの起こり得る処理を書く
FINALLY:
If errNumber <> 0 then
Call Err.Raise(errNumber)
End if
Exit Sub
ERROR_HANDLER:
errNumber = Err.Number
On Error GoTo 0
' ここにエラー発生後の処理を書く
Resume FINALLY
End Sub
実例サンプル
Try-Catch のみの場合
'------------------------------------------------
'
' 関数を呼ぶ関数
'
'------------------------------------------------
Public Sub Caller()
On Error GoTo ERROR_HANDLER
Call Called
Exit Sub
ERROR_HANDLER:
Debug.Print "呼び出し元 : Catch"
End Sub
'------------------------------------------------
'
' 呼び出される(Throwを行う)関数
'
'------------------------------------------------
Public Sub Called()
On Error GoTo ERROR_HANDLER
Dim errNumber as Long
Dim n As Long: n = 1 / 0
Exit Sub
ERROR_HANDLER:
errNumber = Err.Number
On Error GoTo 0
Debug.Print "呼び出し先 : Catch"
' 呼び出し元にエラーを伝達します
Call Err.Raise(errNumber)
End Sub
実行結果
呼び出し先 : Catch
呼び出し元 : Catch
Try-Catch-Finally を再現した場合
'------------------------------------------------
'
' 関数を呼ぶ関数
'
'------------------------------------------------
Public sub Caller()
On Error GoTo ERROR_HANDLER
Call Called
Exit Sub
ERROR_HANDLER:
Debug.Print "呼び出し元 : Catch"
End Sub
'------------------------------------------------
'
' 呼び出される(Throwを行う)関数
'
'------------------------------------------------
Public sub Called()
On Error GoTo ERROR_HANDLER
Dim errNumber as long
Dim n as long: n = 1 / 0
FINALLY:
Debug.Print "呼び出し先 : Finally"
If errNumber <> 0 then
Call Err.Raise(errNumber)
End if
Exit Sub
ERROR_HANDLER:
errNumber = Err.Number
On Error GoTo 0
Debug.Print "呼び出し先 : Catch"
Resume FINALLY
End Sub
実行結果
呼び出し先 : Catch
呼び出し先 : Finally
呼び出し元 : Catch