Ribbit works
記事のトップ画像

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

ExcelVBA

last modified date2021-8-17

publish date2021-8-16

一般的にエラー処理は、各関数では最低限行い、後は呼び出し元に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