5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

VBAのエラー内部構造を解説する

Posted at

はじめに

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度目のエラーでデバッグ画面が表示されるサンプルコード

mdl_Main.bas
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
mdl_Main.bas
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
mdl_Main.bas
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でプロシージャを分割したパターン
mdl_Main.bas
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
mdl_Other.bas
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
mdl_Main.bas
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
mdl_Main.bas
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) エラー発生後の処理でエラーが発生しないようにする例
mdl_Main.bas
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
mdl_Other.bas
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エラー処理のあれこれ」の記載がすべてな気がします。
わかりづらい説明になってしまいましたが、参考になれば幸いです。

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?