実行時エラーの対処方法

実行時エラーの処理は行ラベルを作成して「On Errorステートメント」を記述しておきます。
このエラー処置を行なわない場合の実行時エラーの現象はこのようになります。
型が一致しません。
これは「Integer型(整数型)」の変数に「a」を転記した時に起こります。数字項目にアルファベットを入力するのですから当然エラーになります。ここで「デバッグ」をクリックすると、
エラー処理を行なわない例
このようにソースプログラム上のエラー発生箇所が黄色に表示されます。

エラーの表記をコントロールする場合はこのようにします。

独自にエラー処理している場合は、「デバッグ」は表示されません。このように何のデータでエラーになったのかなどを合わせて表示させることができます。
Option Explicit
'*******************************************************************************
' エラー表記を独自にコントロールする例
'*******************************************************************************
Sub TEST2()
    Dim vrnINPUT As Variant
    Dim intNUM As Integer

    vrnINPUT = "a"          ' 入力データ
    On Error GoTo ERR1
    intNUM = vrnINPUT
    Exit Sub

'-------------------------------------------------------------------------------
' エラー時の飛び先(行ラベル)
ERR1:
    MsgBox "実行時エラー:" & Err.Number & " " & _
        Err.Description & vbCr & _
        "入力データは「" & vrnINPUT & "」です。", vbExclamation, "TEST2"
End Sub
On Error GoTo ...」を書くことでエラー処理が有効となります。逆にエラー処理を無効に戻す場合は、「On Error GoTo 0」と書きます。
エラー処理の飛び先行ラベル「ERR1」は、正常時には動作させないように直前に「Exit Sub」を記述しています。これによりエラーにならない場合は「ERR1」から下の処理は行なわれません。
On Error GoTo ...」はそのプロシージャ内のみ有効です。各プロシージャで記述します。
ちなみに、メッセージ(MsgBox)では、アイコンやタイトルも変更できます。このサンプルの「vbExclamation」だと「注意」の黄色アイコン、「vbCritical」だと「停止」の赤いアイコン、「vbInformation」だと「案内」アイコンとなります。

※ただし、このようなエラー処理は「正常な動作の確認」が取れてから実装するようにして下さい。 最初に説明したように、処理記述のみであれば実行時エラー発生時は「デバッグ」ボタンで発生箇所が黄色で表示されるようになりますが、 このエラー処理を組み込むとエラー処理に流れてしまうため、エラー箇所の特定ができなくなります。

エラー発生時に判断の上、別な値に置き換えて進める場合はこのようにします。

「ゼロに置き換えますか?」で「はい」を選択すると、「a」は「0」に置き換えられて正常終了します。
Option Explicit
'*******************************************************************************
' エラー表記を独自にコントロールする例A
'*******************************************************************************
Sub TEST3()
    Dim vrnINPUT As Variant
    Dim intNUM As Integer

    vrnINPUT = "a"          ' 入力データ
    On Error GoTo ERR2
    intNUM = vrnINPUT
    MsgBox "結果は" & intNUM & "で正常終了しました。"
    Exit Sub

'-------------------------------------------------------------------------------
' エラー時の飛び先(行ラベル)
ERR2:
    If MsgBox("実行時エラー:" & Err.Number & " " & _
        Err.Description & vbCr & _
        "入力データは「" & vrnINPUT & "」です。ゼロに置き換えますか?", _
        vbExclamation + vbYesNo, "TEST3") = vbYes Then
        ' ゼロを上書き
        intNUM = 0
        ' エラー発生箇所のに進める
        Resume Next
    End If

End Sub
このサンプルは、エラー時の表示メッセージを「はい」「いいえ」の問い合わせに変更し、「はい」が選択されたら、「intNUM」には「0」をセットして「Resume Next」でエラーの次行に戻しています。
エラー処理で「0」をセットするのを「vrnINPUT」として、「Resume Next」を「Resume」だけにしても同じ作用になります。Nextのない「Resume」ではエラーの起こった行に戻ってやり直すことができます。
もし、強制的に「0」にして構わないような処理であれば、「If」を取ってしまえばエラー時に何も表示せずに「0」置き換えして進められます。
ちなみに、メッセージ(MsgBox)に「はい」「いいえ」などのボタンを付けることもできるのが判ります。

エラー処理を判断に利用してしまう例もあります。
ファイル内容の表示(正常終了)
これだけ見ても何だか判りませんが、これは「C:\HOGEHOGE.txt」を読み出した内容です。もちろんこのファイルは元々はなかったのですが、ファイルがなければ初期値で作ってしまうというサンプルです。
Option Explicit
'*******************************************************************************
' エラー処理を判断に利用する例
'*******************************************************************************
Sub TEST4()
    Dim strFILE As String
    Dim intFF As Integer
    Dim strREC As String

    On Error GoTo ERR3
    ' ファイルをOPENする
    intFF = FreeFile
    strFILE = "C:\HOGEHOGE.txt"             ' ←読み出すファイル名
    Open strFILE For Input As #intFF        ' ←ファイルがないとエラー発生
    Line Input #intFF, strREC
    Close #intFF
    MsgBox "レコード内容=" & strREC         ' ←終了メッセージ
    Exit Sub

'-------------------------------------------------------------------------------
' エラー時の飛び先(行ラベル)
ERR3:
    Open strFILE For Output As #intFF       ' ←ファイルを生成する
    Print #intFF, "abc"
    Close #intFF
    Resume                                  ' ←エラー箇所に戻る
End Sub

'--------------------------------<< End of Source >>----------------------------
このサンプルでは、エラー処理でメッセージ表示させていません。単純にあるべきファイルがなければ自動的に「初期値="abc"」で作成してしまう処理です。作成したらもう一度エラー箇所(読み出しでOPENした所)に戻って処理を再開します。2度目はエラー処理でファイルが作成されていますから、再度エラーにはなりません。
※このサンプル(TEST4)を動かすと、上記ファイルが本当に作成されます。

複数のプロシージャを組み合わせる場合は?
複数のプロシージャを組み合わせて利用し、それぞれのプロシージャにエラー処理を組み込む場合は注意が必要です。
  • まずは、それぞれのプロシージャに単純にエラー時のエラー表示を組み込んでみます。
    
    '*******************************************************************************
    '   エラー処理サンプル@(各プロシージャでエラー処理)
    '
    '   作成者:井上治  URL:http://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
    '*******************************************************************************
    Option Explicit
    
    '-------------------------------------------------------------------------------
    ' 親プロシージャ
    '-------------------------------------------------------------------------------
    Sub TEST1()
        Const cnsPROC_NAME = "TEST1"
    
        Debug.Print "TEST1_START"
        On Error GoTo TEST1_ERR
        ' 子プロシージャ@の呼び出し
        Call TEST2
        ' 子プロシージャAの呼び出し
        Call TEST3
        MsgBox "終了しました。"
        GoTo TEST1_END
    
    TEST1_ERR:
        Debug.Print "TEST1_ERR"
        MsgBox cnsPROC_NAME & " " & Err.Number & " " & Err.Description
    
    TEST1_END:
        Debug.Print "TEST1_END"
    End Sub
    
    '-------------------------------------------------------------------------------
    ' 子プロシージャ@
    '-------------------------------------------------------------------------------
    Private Sub TEST2()
        Const cnsPROC_NAME = "TEST2"
    
        Debug.Print "TEST2_START"
        On Error GoTo TEST2_ERR
        Err.Raise 100,, "abc"      ' ←ここでエラーを発生させます。
        GoTo TEST2_END
    
    TEST2_ERR:
        Debug.Print "TEST2_ERR"
        MsgBox cnsPROC_NAME & " " & Err.Number & " " & Err.Description
    
    TEST2_END:
        Debug.Print "TEST2_END"
    End Sub
    
    '-------------------------------------------------------------------------------
    ' 子プロシージャA
    '-------------------------------------------------------------------------------
    Private Sub TEST3()
        Const cnsPROC_NAME = "TEST3"
    
        Debug.Print "TEST3_START"
        On Error GoTo TEST3_ERR
        Err.Raise 100,, "abc"      ' ←ここでエラーを発生させます。
        GoTo TEST3_END
    
    TEST3_ERR:
        Debug.Print "TEST3_ERR"
        MsgBox cnsPROC_NAME & " " & Err.Number & " " & Err.Description
    
    TEST3_END:
        Debug.Print "TEST3_END"
    End Sub
    
    '--------------------------------<< End of Source >>----------------------------
    
    親プロシージャ「TEST1」から、子プロシージャ「TEST2」と「TEST3」が順次呼び出される処理です。 ここでは子プロシージャ「TEST2」と「TEST3」で、Err.Raiseを使って実行時エラーを起こさせています。
    この時の処理結果は、
    2つのエラーが順次起きる
    このように2つのエラーが順次起きます。イミディエイトウィンドウを見ると、
    イミディエイトウィンドウ
    このようになっていて、子プロシージャ「TEST2」内でエラーが起きても、そのまま「TEST3」も呼ばれてしまい、「終了しました。」も表示されてしまいます。 エラー処理時にEndステートメントを使って強引に止めてしまう方法もありますが、それでは単にエラー処理を行なわないで実行時エラーで止まってしまうのと変わりません。

  • エラー処理記述があったり、なかったりも誤動作につながります。
    子プロシージャ2つの一方でエラー処理記述を忘れたとしてみましょう。
    
    '*******************************************************************************
    '   エラー処理サンプルA(各プロシージャでエラー処理)
    '
    '   作成者:井上治  URL:http://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
    '*******************************************************************************
    Option Explicit
    
    '-------------------------------------------------------------------------------
    ' 親プロシージャ
    '-------------------------------------------------------------------------------
    Sub TEST1()
        Const cnsPROC_NAME = "TEST1"
    
        Debug.Print "TEST1_START"
        On Error GoTo TEST1_ERR
        ' 子プロシージャ@の呼び出し
        Call TEST2
        ' 子プロシージャAの呼び出し
        Call TEST3
        MsgBox "終了しました。"
        GoTo TEST1_END
    
    TEST1_ERR:
        Debug.Print "TEST1_ERR"
        MsgBox cnsPROC_NAME & " " & Err.Number & " " & Err.Description
    
    TEST1_END:
        Debug.Print "TEST1_END"
    End Sub
    
    '-------------------------------------------------------------------------------
    ' 子プロシージャ@
    '-------------------------------------------------------------------------------
    Private Sub TEST2()
        Const cnsPROC_NAME = "TEST2"
    
        Debug.Print "TEST2_START"
        On Error GoTo TEST2_ERR
        GoTo TEST2_END
    
    TEST2_ERR:
        Debug.Print "TEST2_ERR"
        MsgBox cnsPROC_NAME & " " & Err.Number & " " & Err.Description
    
    TEST2_END:
        Debug.Print "TEST2_END"
    End Sub
    
    '-------------------------------------------------------------------------------
    ' 子プロシージャA
    '-------------------------------------------------------------------------------
    Private Sub TEST3()
        Const cnsPROC_NAME = "TEST3"
    
        Debug.Print "TEST3_START"
    '    On Error GoTo TEST3_ERR    ' ←これを書き忘れたとします。
        Err.Raise 100,, "abc"      ' ←ここでエラーを発生させます。
        GoTo TEST3_END
    
    TEST3_ERR:
        Debug.Print "TEST3_ERR"
        MsgBox cnsPROC_NAME & " " & Err.Number & " " & Err.Description
    
    TEST3_END:
        Debug.Print "TEST3_END"
    End Sub
    
    '--------------------------------<< End of Source >>----------------------------
    
    今回は、子プロシージャ「TEST2」ではエラーは起こしません。 子プロシージャ「TEST3」の方は「On Error Goto」の記述を行なわないでエラーを起こしてみます。
    動作させると、
    エラーが起きる
    このように表示されます。イミディエイトウィンドウを見ると、
    イミディエイトウィンドウ
    このようになっています。「TEST3_ERR」や「TEST3_END」の表示が出ていないのが分かると思います。これはつまり、子プロシージャ「TEST3」内で発生した実行時エラーが親プロシージャ「TEST1」のエラー処理記述の方でトラップされたことを示しています。
    エラー処理記述を書いたり書かなかったり(忘れたり)が危険なことが分かると思います。

  • 前処理でのエラー発生時に後続処理を行なわないようにするには。
    子プロシージャ内で発生したエラーを単にその子プロシージャ内で表示するのではなく、親プロシージャに通知するようにしたのがこのサンプルです。
    
    '*******************************************************************************
    '   エラー処理サンプルB(各プロシージャでエラー処理)
    '
    '   作成者:井上治  URL:http://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
    '*******************************************************************************
    Option Explicit
    
    '-------------------------------------------------------------------------------
    ' 親プロシージャ
    '-------------------------------------------------------------------------------
    Sub TEST1()
        Const cnsPROC_NAME = "TEST1"
        Dim strMSG As String
    
        Debug.Print "TEST1_START"
        On Error GoTo TEST1_ERR
        ' 子プロシージャ@の呼び出し
        strMSG = TEST2()
        ' 子プロシージャAの呼び出し
        If strMSG = "" Then strMSG = TEST3()
        ' 終了確認
        If strMSG = "" Then
            MsgBox "終了しました。"
        Else
            MsgBox strMSG, vbExclamation
        End If
        GoTo TEST1_END
    
    TEST1_ERR:
        Debug.Print "TEST1_ERR"
        MsgBox cnsPROC_NAME & " " & Err.Number & " " & Err.Description
    
    TEST1_END:
        Debug.Print "TEST1_END"
    End Sub
    
    '-------------------------------------------------------------------------------
    ' 子プロシージャ@
    '-------------------------------------------------------------------------------
    Private Function TEST2() As String
        Const cnsPROC_NAME = "TEST2"
    
        Debug.Print "TEST2_START"
        On Error GoTo TEST2_ERR
        Err.Raise 100,, "abc"      ' ←ここでエラーを発生させます。
        GoTo TEST2_END
    
    TEST2_ERR:
        Debug.Print "TEST2_ERR"
        TEST2 = cnsPROC_NAME & " " & Err.Number & " " & Err.Description
    
    TEST2_END:
        Debug.Print "TEST2_END"
    End Function
    
    '-------------------------------------------------------------------------------
    ' 子プロシージャA
    '-------------------------------------------------------------------------------
    Private Function TEST3() As String
        Const cnsPROC_NAME = "TEST3"
    
        Debug.Print "TEST3_START"
        On Error GoTo TEST3_ERR
        Err.Raise 100,, "abc"      ' ←ここでエラーを発生させます。
        GoTo TEST3_END
    
    TEST3_ERR:
        Debug.Print "TEST3_ERR"
        TEST3 = cnsPROC_NAME & " " & Err.Number & " " & Err.Description
    
    TEST3_END:
        Debug.Print "TEST3_END"
    End Function
    
    '--------------------------------<< End of Source >>----------------------------
    
    このように、子プロシージャはFunctionプロシージャに変更して、エラー発生時はエラーメッセージを親プロシージャに返します。 親プロシージャは戻り値がブランクでなければ後続処理は行なわずにエラーを表示させて終わるようにします。
    動作させると、
    エラーが起きる
    このように表示されます。イミディエイトウィンドウを見ると、
    イミディエイトウィンドウ
    このように、子プロシージャ「TEST2」でエラーが起きているため「TEST3」は行なわれずに「TEST2」で作成されたエラーメッセージが表示されます。

  • 少し「無精」なやり方も考えられます。
    1つ前の「エラー処理記述を書いたり書かなかったり(忘れたり)」を逆用してみます。
    
    '*******************************************************************************
    '   エラー処理サンプルC(親プロシージャでエラー処理)
    '
    '   作成者:井上治  URL:http://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
    '*******************************************************************************
    Option Explicit
    Private g_strMSG As String
    
    '-------------------------------------------------------------------------------
    ' 親プロシージャ
    '-------------------------------------------------------------------------------
    Sub TEST1()
        Debug.Print "TEST1_START"
        On Error GoTo TEST1_ERR
        g_strMSG = "TEST1"
        ' 子プロシージャ@の呼び出し
        Call TEST2
        ' 子プロシージャAの呼び出し
        Call TEST3
        MsgBox "終了しました。"
        GoTo TEST1_END
    
    TEST1_ERR:
        Debug.Print "TEST1_ERR"
        MsgBox g_strMSG & " " & Err.Number & " " & Err.Description
    
    TEST1_END:
        Debug.Print "TEST1_END"
    End Sub
    
    '-------------------------------------------------------------------------------
    ' 子プロシージャ@
    '-------------------------------------------------------------------------------
    Private Sub TEST2()
        Debug.Print "TEST2_START"
        g_strMSG = "TEST2"
        Err.Raise 100,, "abc"      ' ←ここでエラーを発生させます。
        Debug.Print "TEST2_END"
    End Sub
    
    '-------------------------------------------------------------------------------
    ' 子プロシージャA
    '-------------------------------------------------------------------------------
    Private Sub TEST3()
        Debug.Print "TEST3_START"
        g_strMSG = "TEST3"
        Err.Raise 100,, "abc"      ' ←ここでエラーを発生させます。
        Debug.Print "TEST3_END"
    End Sub
    
    '--------------------------------<< End of Source >>----------------------------
    
    これはどうでしょうか。記述は結構シンプルになりました。
    エラー処理記述を親プロシージャだけに書いて、子プロシージャは単純に処理記述だけにしてしまいます。
    動作させてみます。
    エラーが起きる
    このように表示されます。イミディエイトウィンドウを見ると、
    イミディエイトウィンドウ
    このように、子プロシージャ「TEST2」でエラーが起きて即座に親プロシージャ「TEST1」のエラートラップに飛んでしまいます。 単にこの状態にすると、エラーが発生したプロシージャが分からないので、モジュールレベルの変数にプロシージャ名をセットしているわけです。

    エラーメッセージだけのことであればこのようなエラー処理でも構わないのですが、複雑な処理ではエラー処理時にたとえば「開いているファイルを閉じる」とかプロシージャごとの固有の「後始末」が発生することが多いので、このような方法が使えるケースは少ないと思います。