vba · DOCS

VBAにおけるTry-Catch-Finallyの実装

にメンテナンス済み

一般的にエラー処理は、各関数では最低限行い、後は呼び出し元に 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 の引数に説明文を渡しておけば、呼び出し元でも元のエラー内容を確認できます。

処理の流れは次のとおりです。

  1. エラーが発生し、エラーハンドラに飛ぶ
  2. エラー情報を変数に退避する
  3. On Error GoTo 0 でエラーハンドラを解除する
  4. 後処理(Close など)を行う
  5. 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
  • #Excel
  • #VBA