はじめに
VBAに限らずですが、開発をしているときに必ずエラーをどうするかは頭を使う場所だと思います。
私がVBAで開発を行った際にもかなり頭を悩ませました。
特にVBAがエラーを内部でどう処理してどう管理しているのか、内部構造がよくわからず苦労しました。
ググってもググっても自分が納得するような説明が見当たらず、途方に暮れた覚えがあります。
しかし、開発をしていく中で仮説を立てながら実験し、かなり説明力のありそうな内部構造を見出すことができました。
過去の自分のため、同様の疑問を持っている方のためになればという思いで、稚拙ながらVBAのエラー処理の内部構造を解説してみたいと思います。
内部構造解説の構成
- ■VBAエラー処理のあれこれ
- 最初に、エラーに関わるステートメントやプロパティ情報、VBAのエラー処理ルールを列挙していきます。
- ■サンプルコードで確認しよう ~プロシージャが1つの場合のエラーの流れ~
- そして、そのルールなどをサンプルコードを見ながら、プロシージャが1つの場合の基本的なエラーの流れを確認します。
- ■サンプルコードで確認しよう ~プロシージャが複数の場合のエラーの流れ~
- さらに、プロシージャが複数の場合に関係してくるルールを説明をしていきます。
■VBAエラー処理のあれこれ
- 1. プロシージャのエラープロパティ
- プロシージャは
- 1. エラーステータス(エラー状態 or 非エラー状態)
- 2. エラー発生行
- 3. エラーラベル(エラー時の行先)
- を持つ
- ※ 4. 呼び出しごとに初期化される。
- 2. プロシージャのエラー処理ルール
- プロシージャはエラー発生時、以下のルールに従ってエラーを対処する
- 1. プロシージャのエラープロパティを設定する
- 1. エラーステータスをエラー状態にする
- 2. エラー発生行を記録する
- 2. 実行行をエラーラベルに飛ばし処理を継続する
- 3. ただし、対応できないエラー(※)が発生した場合、呼び出し元にエラーを投げる
- ※ 1. エラーラベル未設定時のエラー
- ※ 2. エラー状態でエラーが発生する
- 4. 呼び出し元プロシージャはエラー発生プロシージャの呼び出し行でエラーが発生したとしてエラー処理を行う
- 5. 対応できないエラーが発生し、呼び出し元プロシージャがない場合はデバッグ画面を表示する
- 3. On Errorステートメントの処理内容
- 1. エラーラベル更新
- 1. On Error Go To 0はエラーラベルの初期化に相当する
- 2. エラー状態の場合はエラーラベルの更新はできない
- 2. ErrオブジェクトのClearが行われる
- 1. On Error Resume Nextでのエラー対処ではErrオブジェクトのClearは行われない
- 4. Resumeステートメントの処理内容
- 1. ステートメントの移動(GoTo文相当)
- 1.「Resume Next」の場合はエラー発生行の次の行へ移動する
- 2. ErrオブジェクトのClear
- 3. エラーステータスの解除(エラー状態→非エラー状態)
- 4. 非エラー状態での実行エラー発生が行われる
- 5. Errオブジェクトについて
- 1. グローバル変数と同等
どこかのプロシージャでErrオブジェクトがClearされると、Errオブジェクトの内容は失われてしまう。
■サンプルコードで確認しよう ~プロシージャが1つの場合のエラーの流れ~
(1)2度目のエラーでデバッグ画面が表示されるサンプルコード
Sub Sample_Error()
Dim i As Long
Dim sheetName As String
On Error GoTo Nosheet '<1>
For i = 1 To 6
sheetName = "No" & i
ThisWorkbook.Sheets(sheetName).Activate
Range("A1").Value = "このシートの名前は" & sheetName & "です" '<2>,<3>
Nosheet:
Next i
End Sub
<1>On Error GoTo Nosheetステートメントを実行
→エラーラベルを「NoSheet」に設定する(1-3)
→errオブジェクトのClearをする(1-3)
<2>i=3([No3]シート)でエラーが発生
→非エラー状態→エラー状態となる。(1-1)
→エラーラベルの通りNoSheetへ移動。(2-2)
<3>i=6([No6]シート)でエラーが発生
→エラー状態でのエラーが発生。(1-1)
→対応できないエラー(エラー状態でエラーが発生)となる。(2-3-2)
→呼び出し元プロシージャがないため、デバッグ画面が表示される(2-4)
(2) 2度目のエラーでもデバッグ画面を表示させない例1
Sub Sample_NotError1()
Dim i As Long
Dim sheetName As String
On Error GoTo Nosheet '<1>
For i = 1 To 6
sheetName = "No" & i
ThisWorkbook.Sheets(sheetName).Activate
Range("A1").Value = "このシートの名前は" & sheetName & "です" '<2>,<3>
continue:
Next i
Exit Sub
Nosheet:
Resume continue
End Sub
<1>On Error GoTo Nosheetステートメントを実行
→エラーラベルを「NoSheet」に設定する。(1-3)
→errオブジェクトのClearをする。(3-2)
<2>i=3([No3]シート)でエラーが発生
→非エラー状態→エラー状態となる。(2-1-1)
→エラーラベルの通りNoSheetへ移動。(2-2)
→Resume continueステートメントを実行
errオブジェクトがClearされる。(4-2)
エラー状態→非エラー状態となる。(4-3)
→continueラベルから処理を継続(4-1)
<3>i=6([No6]シート)でエラーが発生
→エラーラベルの通りNoSheetへ移動。(2-2)
非エラー状態のため、「対応できないエラー」とならず処理が継続
→i=3のエラー処理と同じ処理が行われる。
(3) 2度目のエラーでもデバッグ画面を表示させない例2
Sub Sample_NotError2()
Dim i As Long
Dim sheetName As String
On Error Resume Next '<1>
For i = 1 To 6
sheetName = "No" & i
ThisWorkbook.Sheets(sheetName).Activate '<2>,<3>
Range("A1").Value = "このシートの名前は" & sheetName & "です"
Next i
On Error GoTo 0 '<4>
End Sub
<1>On Error Resume Nextステートメントを実行
→エラーラベルを「NoSheet」に設定する(1-3)
→errオブジェクトのClearをする(3-2)
<2>i=3([No3]シート)でエラーが発生
→非エラー状態→エラー状態となる。(2-1-1)
→エラーラベルが「Resume Next」のため、
エラー発生行の次の行へ移動する。(4-1)
ErrオブジェクトのClear(4-2)
エラーステータスの解除(エラー状態→非エラー状態)(4-3)
が実行され、処理継続。
<3>i=6([No6]シート)でエラーが発生
→非エラー状態→エラー状態となる。(2-1-1)
→エラーラベルが「Resume Next」のため、
エラー発生行の次の行へ移動する。(4-1)
ErrオブジェクトのClear(4-2)
エラーステータスの解除(エラー状態→非エラー状態)(4-3)
が実行され、処理継続。
<4>On Error GoTo 0ステートメントを実行
→Go To 0のため、
エラーラベルの初期化(3-1-1)
ErrオブジェクトのClear(3-2)
が実行される。
<追記>
このサンプルプログラムでは不要であるが、On Error GoTo 0を書くことで
- On Error Resume Next~On Error GoTo 0でエラーが握りつぶされる範囲が明示される
- Errオブジェクトをクリアしておくことで握りつぶしたエラーの情報をきれいにしておける
のでOn Error GoTo 0ステートメントを付け加えるのがおすすめ。
■サンプルコードで確認しよう ~プロシージャが複数の場合のエラーの流れ~
(4) Sample_Errorでプロシージャを分割したパターン
Sub Sample_Main_ErrorAfterError1()
Dim i As Long
Dim sheetName As String
Dim errMsgs(5) As String
On Error GoTo Nosheet '<1>
For i = 1 To 6
sheetName = "No" & i
Call mdl_Other.WriteSheetName(sheetName, "A1") '<2>,<3>
continue:
Next i
ThisWorkbook.Sheets("エラー").Range("A1") = errMsgs
Exit Sub
Nosheet:
' エラーの記録
errMsgs(i) = sheetName & "の処理でエラーが発生しました。" & vbCr & Err.Description
Resume continue
End Sub
Sub WriteSheetName( _
ByVal target_sheet_name As String, ByVal target_range As String _
)
ThisWorkbook.Sheets(target_sheet_name).Activate
Range(target_range).Value = "このシートの名前は" & target_sheet_name & "です"
End Sub
<1>On Error GoTo Nosheetステートメントを実行
→エラーラベルを「NoSheet」に設定する。(1-3)
→errオブジェクトのClearをする。(3-2)
<2>i=3([No3]シート)でエラーが発生
→非エラー状態→エラー状態となる。(2-1)
→対応できないエラー(※)が発生したため、エラーを呼び出し元に投げる。(2-3)
※エラーラベル未設定時のエラー(2-3-1)
→Call WriteSheetName(sheetName, "A1")ステートメントのエラーとして処理継続(Sample_Main_Error)(2-4)
→エラーラベルの通りNoSheetへ移動。(2-2)
<3>i=6([No6]シート)でエラーが発生
→非エラー状態→エラー状態となる。(2-1)
呼び出しごとでプロシージャのエラープロパティは初期化され、非エラー状態になっていることに注意
→対応できないエラー(※)が発生したため、エラーを呼び出し元に投げる(2-3)
※エラーラベル未設定時のエラー(2-3-1)
→Call WriteSheetName(sheetName, "A1")ステートメントのエラー(2-4)
エラー状態でのエラーとなる。
→対応できないエラーとなる。(2-3)
※エラー状態でエラーが発生。(2-3-2)
→呼び出し元プロシージャがないため、デバッグ画面が表示される。(2-5)
(5) エラー発生後の処理でエラーが発生してしまう例1
Sub Sample_Main_ErrorAfterError1()
Dim i As Long
Dim sheetName As String
Dim errMsgs(5) As String
On Error GoTo Nosheet
For i = 1 To 6
sheetName = "No" & i
Call mdl_Other.WriteSheetName(sheetName, "A1") '<1>
continue:
Next i
ThisWorkbook.Sheets("エラー").Range("A1") = errMsgs
Exit Sub
Nosheet:
' エラーの記録
errMsgs(i) = sheetName & "の処理でエラーが発生しました。" & vbCr & Err.Description '<2>
Resume continue
End Sub
<1>i=6([No6]シート)の時、Call WriteSheetName(sheetName, "A1")でエラー発生
→エラーラベルの通りNoSheetへ移動。(2-2)
<2>エラーの記録処理でエラーが発生
※errMsgs配列が0から5までのため、「インデックスが有効範囲にありません。」エラーが出る。
→対応できないエラー(エラー状態でエラーが発生)となる。(2-3)
→呼び出し元プロシージャがないため、デバッグ画面が表示される。(2-5)
(6) エラー発生後の処理でエラーが発生してしまう例2
Sub Sample_Main_ErrorAfterError2()
Dim i As Long
Dim sheetName As String
Dim errMsgs(5) As String
On Error GoTo Nosheet
For i = 1 To 6
sheetName = "No" & i
Call mdl_Other.WriteSheetName(sheetName, "A1") '<1>
continue:
Next i
ThisWorkbook.Sheets("エラー").Range("A1") = errMsgs
Exit Sub
Nosheet:
On Error Resume Next '<2>
' エラーの記録
errMsgs(i) = sheetName & "の処理でエラーが発生しました。" & vbCr & Err.Description
Resume continue
End Sub
<1>i=6([No6]シート)の時、Call WriteSheetName(sheetName, "A1")でエラー発生
→エラーラベルの通りNoSheetへ移動。(2-2)
<2>On Error Resume Nextステートメントを実行
しかし、エラー状態の場合はエラーラベル更新をできない(3-1-2)
→Resume Nextがエラーラベルにセットされない
→サンプルコード(5)と同様、デバッグ画面が表示されてしまう
(7) エラー発生後の処理でエラーが発生しないようにする例
Sub Sample_Main_NotErrorAfterError()
Dim i As Long
Dim sheetName As String
Dim errMsgs(5) As String
Dim errMsg As String
Dim errDescription As String
On Error GoTo Nosheet
For i = 1 To 6
sheetName = "No" & i
Call mdl_Other.WriteSheetName(sheetName, "A1") '<1>,<3>
continue:
Next i
ThisWorkbook.Sheets("エラー").Range("A1") = errMsgs
Exit Sub
Nosheet:
errDescription = Err.Description
' エラーの記録
Call AppendErrorMsg(errMsgs, i, errMsg) '<2>,<4>
Resume continue
End Sub
Sub WriteSheetName( _
ByVal target_sheet_name As String, ByVal target_range As String _
)
ThisWorkbook.Sheets(target_sheet_name).Activate
Range(target_range).Value = "このシートの名前は" & target_sheet_name & "です"
End Sub
Sub AppendErrorMsg( _
ByRef err_msgs As Variant, ByVal arr_num As Long, ByVal err_msg As String _
)
On Error Resume Next
err_msgs(arr_num) = err_msg
On Error GoTo 0
End Sub
<1>i=3([No6]シート)の時、Call WriteSheetName(sheetName, "A1")でエラー発生
→エラーラベルの通りNoSheetへ移動。(2-2)
<2>エラーの記録処理実行(AppendErrorMsg)
→On Error Resume Nextステートメントを実行
→i=3ではエラー発生しないためエラーなく処理が継続する
<3>i=6([No6]シート)の時、Call WriteSheetName(sheetName, "A1")でエラー発生
→エラーラベルの通りNoSheetへ移動。(2-2)
<4>エラーの記録処理実行(AppendErrorMsg)
→On Error Resume Nextステートメントを実行
→i=6ではエラー発生するが、エラーラベルが「Resume Next」であるため、エラーなく処理が継続する
【ポイント】
エラー記録処理を別プロシージャにくくりだし、そこでOn Error Resume Nextでエラーを握りつぶす。
そうすればSample_Main_NotErrorAfterErrorのエラー処理内ではエラーが発生していないことになる。
【注意】
AppendErrorMsgプロシージャでOn Error Resume Nextステートメントを実行しているため、ErrオブジェクトがClearされることに注意。
それを見越して、エラー処理内でerrDescription = Err.Descriptionとしてエラー内容を保持している。
さいごに
おおよそ一通り解説してみました。
本質的な内容としては「■VBAエラー処理のあれこれ」の記載がすべてな気がします。
わかりづらい説明になってしまいましたが、参考になれば幸いです。