VBAでエラー処理とThrow処理を両方行う方法

記事のトップ画像

一般的にエラー処理は、各関数では最低限行い、後は呼び出し元にThrowするのが一般的かと思いますが、VBAではそれを簡単には実装できません。

エラーハンドラを作ってしまうとエラーを握りつぶしてしまい、そこから再度Err.Raiseしようとすると、その関数内でエラーを拾い無限ループになるからです。

具体的な例を挙げると、

  • Open関数でファイルを開いた後、エラーになった場合でもCloseは発生させたい。
  • その上で、エラーを呼び出し元に伝達したい。

といった場合です。

On Error GoTo 0
Call Err.Raise(Err.Number)

解決には上記のコードを使います。解説とサンプルを紹介致します。

On Error GoTo 0

On Error GoTo 0 を使用すると、使用した以前に定義していたOn Errorを打ち消すことができます。

通常、エラーハンドラでErr.Raiseを書いてしまうと、

  1. エラーを拾う
  2. エラーハンドラに飛ぶ
  3. エラーを投げる
  4. 1に戻る

上記のループになってしまいますが、On Error GoTo 0 を入れることで、

  1. エラーを拾う
  2. エラーハンドラに飛ぶ
  3. エラーハンドラの打ち消し
  4. エラーを呼び出し元にスロー

とすることができます。これにより、他のプログラム言語で言うところの、catchにthrowを含むようなTry-Catch-Finallyを再現できます。

コードサンプル

エラー処理 → Throwだけでいい場合

Public sub Caller()
    On Error GoTo ERROR_HANDLER
    Call Called

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

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
        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

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

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
        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