On Error Resume Next を書いておけばエラーが出ても止まらないから安心」と思って使い始めたら、どこでエラーが起きているのかまったくわからなくなった――これはVBAを使い始めた人が必ずといっていいほど経験するトラブルです。On Error Resume Next はエラーをなかったことにするのではなく、エラー情報を Err オブジェクトに保持しながらコードを続行するという動作をします。使い方を誤ると、処理が壊れたままサイレントに進んでいく「最悪のデバッグ地獄」に陥ります。本記事では、On Error Resume Next の正しい仕組みから、エラー箇所の特定方法、適切な使い方、そしてプロジェクト全体を守るエラーハンドリング設計まで、コード付きで徹底解説します。


目次


On Error Resume Nextとは何か――誤解されやすい動作の仕組み

On Error Resume Next は、「エラーが発生したときにプログラムを止めず、エラーが起きた行の次の行から処理を続行する」という命令です。エラーは消えるのではなくErr オブジェクトに保存されます。コードを続行しながら、後から Err.Number でエラーが起きたかどうかを確認できる設計になっています。

' On Error Resume Nextの動作を確認するコード
Sub UnderstandOERN()

    On Error Resume Next

    Dim x As Long
    x = 1 / 0   ' ゼロ除算エラー(エラー11)が発生

    ' エラーが起きても次の行に進む
    Debug.Print "x = " & x   ' x = 0(エラーで代入されなかったまま)

    ' Errオブジェクトにエラー情報が残っている
    Debug.Print "Err.Number      = " & Err.Number       ' → 11
    Debug.Print "Err.Description = " & Err.Description  ' → 0 で除算しました

    On Error GoTo 0   ' エラー処理を元に戻す

End Sub

ここで重要なのは、On Error Resume Next はエラーを握りつぶすのではなく、「エラーが起きても止まらない状態にする」命令だということです。エラー情報は Err オブジェクトに残るため、直後に Err.Number を確認すれば「エラーが起きたかどうか」を判断できます。

問題が起きるのは、On Error Resume Next を書いたまま Err.Number の確認をせず、さらに Err.Clear もしないで処理を続けるという使い方です。これをやると複数のエラーが積み重なり、どこで何が起きたか追跡不能になります。


どこでエラーが出ているかを特定する方法

On Error Resume Next を外すとどこかでエラーが止まる」「でも外すと業務が動かない」という状況で、エラー箇所を特定する方法を解説します。

方法1:On Error Resume Nextを一時的に外してF8ステップ実行する

最もシンプルで確実な方法です。On Error Resume Next の行をコメントアウトしてから、VBAエディタでF8キーを押してステップ実行します。エラーが起きた行で処理が止まり、黄色くハイライトされます。

' デバッグ時:On Error Resume Nextをコメントアウトして原因を特定
Sub FindErrorStep()

    ' On Error Resume Next   ' ← 一時的にコメントアウトしてステップ実行

    Dim ws  As Worksheet
    Dim rng As Range

    Set ws  = ThisWorkbook.Sheets("データ")   ' ← ここでエラーが止まればシート名が原因
    Set rng = ws.Range("A1:A100")              ' ← ここで止まればRange指定が原因

    Dim val As Long
    val = rng.Cells(1, 1).Value               ' ← ここで止まれば値の型が原因

    Debug.Print val

End Sub

方法2:各行の後にErr.Numberを確認するデバッグコードを挿入する

ステップ実行が難しい長いコードや、ループ処理の中でエラーが起きている場合は、疑わしい範囲の各行の後に Err.Number チェックを挟んで原因を絞り込みます。

' ========================================
' 各行でErr.Numberを確認するデバッグパターン
' ========================================
Sub DebugWithErrCheck()

    On Error Resume Next

    ' --- ステップ1 ---
    Err.Clear
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("データ")
    If Err.Number <> 0 Then
        Debug.Print "ステップ1でエラー: " & Err.Number & " / " & Err.Description
        Err.Clear
        ' 必要なら処理を止める
        ' On Error GoTo 0
        ' Exit Sub
    End If

    ' --- ステップ2 ---
    Err.Clear
    Dim rng As Range
    Set rng = ws.Range("A1:A100")
    If Err.Number <> 0 Then
        Debug.Print "ステップ2でエラー: " & Err.Number & " / " & Err.Description
        Err.Clear
    End If

    ' --- ステップ3 ---
    Err.Clear
    Dim val As Long
    val = rng.Cells(1, 1).Value
    If Err.Number <> 0 Then
        Debug.Print "ステップ3でエラー: " & Err.Number & " / " & Err.Description
        Err.Clear
    End If

    On Error GoTo 0

    Debug.Print "処理完了: val = " & val

End Sub

方法3:行番号付きのエラートレースを実装する

' ========================================
' 行番号トレース付きのデバッグコード
' 長いプロシージャのどのブロックでエラーが起きているかを特定する
' ========================================
Sub TraceErrorLocation()

    On Error Resume Next

    Dim tracePoint As String   ' 現在の処理ポイントを記録する変数

    ' トレースポイントを設定しながら処理を進める
    tracePoint = "1: シート取得"
    Err.Clear
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("集計")
    If Err.Number <> 0 Then GoTo TraceError

    tracePoint = "2: 最終行取得"
    Err.Clear
    Dim lastRow As Long
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
    If Err.Number <> 0 Then GoTo TraceError

    tracePoint = "3: データ読み込み"
    Err.Clear
    Dim data As Variant
    data = ws.Range("A2:C" & lastRow).Value
    If Err.Number <> 0 Then GoTo TraceError

    tracePoint = "4: 計算処理"
    Err.Clear
    Dim i As Long
    For i = 1 To UBound(data, 1)
        ws.Cells(i + 1, 4).Value = CDbl(data(i, 2)) * CDbl(data(i, 3))
        If Err.Number <> 0 Then
            tracePoint = "4: 計算処理(" & i & "行目)"
            GoTo TraceError
        End If
    Next i

    On Error GoTo 0
    MsgBox "処理完了", vbInformation
    Exit Sub

TraceError:
    Dim errNum  As Long
    Dim errDesc As String
    errNum  = Err.Number
    errDesc = Err.Description
    Err.Clear
    On Error GoTo 0

    MsgBox "エラー発生箇所: " & tracePoint & vbCrLf & vbCrLf & _
           "エラー番号: " & errNum & vbCrLf & _
           "内容: " & errDesc, vbCritical, "エラートレース"

    Set ws = Nothing

End Sub

方法4:イミディエイトウィンドウでErr.Numberを逐次確認する

' VBAエディタで Ctrl+G を押してイミディエイトウィンドウを開き、
' ステップ実行中(F8)に以下を入力してエラー状態を確認する

' ? Err.Number        → 現在のエラー番号(0ならエラーなし)
' ? Err.Description   → 現在のエラーの説明文
' ? Err.Source        → エラー発生元の情報

' ステップ実行とイミディエイトウィンドウを組み合わせると
' どの変数がどの状態のときにエラーが起きているかを
' リアルタイムで追跡できる

Errオブジェクトの正しい使い方

On Error Resume Next を正しく使うために、Err オブジェクトのプロパティと動作を正確に理解しておく必要があります。

Errオブジェクトのプロパティ

  • Err.Number:エラー番号。エラーが起きていない場合は 0On Error Resume Next の後でこれを確認してエラーの有無を判断する
  • Err.Description:エラーの説明文(日本語)。デバッグ時のログ記録に使う
  • Err.Source:エラーが発生したオブジェクトまたはアプリケーションの名前
  • Err.ClearErr.Number を0にリセットする。次のエラーチェックを正確に行うために、チェックの直前に必ず呼ぶ

Err.Clearが必須な理由

' ========================================
' Err.Clearを忘れると前のエラーが残り続ける
' ========================================
Sub ErrClearImportance()

    On Error Resume Next

    ' 1回目のエラーを起こす
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("存在しないシート")   ' エラー9発生

    Debug.Print "1回目のErr.Number: " & Err.Number   ' → 9

    ' Err.Clearせずに次の操作へ
    Dim x As Long
    x = 100   ' エラーは起きないはず

    Debug.Print "2回目のErr.Number: " & Err.Number   ' → 9 のまま!(クリアされていない)
    ' → x = 100の代入は成功しているのに、まだエラー9が残っている
    '   これが「どこでエラーが出ているか不明」の大きな原因

    ' ---- 正しい書き方 ----
    Err.Clear   ' チェックの前に必ずクリア

    Dim y As Long
    y = 200   ' エラーなし

    If Err.Number <> 0 Then
        Debug.Print "y の代入でエラー: " & Err.Number
    Else
        Debug.Print "y の代入は正常: y = " & y   ' → ここが表示される
    End If

    On Error GoTo 0

End Sub

Errオブジェクトが自動クリアされるタイミング

Err.Number は以下のタイミングで自動的に0にリセットされます。これを知らないと「さっきエラーが出ていたのにErr.Numberが0になっている」という混乱が起きます。

  • Err.Clear を呼んだとき
  • ResumeResume NextResume ラベル を実行したとき
  • On Error GoTo 0 を実行したとき
  • プロシージャを終了したとき
  • 新しいエラーが発生したとき(古いエラーは上書きされる)
' ========================================
' Errオブジェクトの自動クリアを意識したコード
' ========================================
Sub ErrAutoClearDemo()

    On Error Resume Next

    ' エラーを起こす
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("不在シート")

    ' ここでErr.Numberは9
    Debug.Print "エラー直後: " & Err.Number   ' → 9

    ' 別のエラーを起こすと上書きされる
    Dim x As Long
    x = 1 / 0   ' エラー11(ゼロ除算)

    Debug.Print "上書き後: " & Err.Number     ' → 11(9は消えた)

    ' On Error GoTo 0でクリアされる
    On Error GoTo 0

    ' ここでErr.Numberは0
    Debug.Print "GoTo 0後: " & Err.Number     ' → 0

End Sub

やってはいけないOn Error Resume Nextの使い方

以下のパターンは「どこでエラーが起きているかわからない」状態を生む典型的なアンチパターンです。既存コードにこれらが含まれていないか確認してください。

アンチパターン1:プロシージャ全体をOn Error Resume Nextで包む

' NG:プロシージャ全体をOn Error Resume Nextで包んでいる
' エラーがどこで起きても処理が続行されるため、
' 誤った結果が出力されてもエラーに気づけない
Sub BadPattern1()

    On Error Resume Next   ' ← プロシージャの先頭でオン

    Dim ws      As Worksheet
    Dim lastRow As Long
    Dim i       As Long

    Set ws      = ThisWorkbook.Sheets("データ")   ' シートがなくてもスルー
    lastRow     = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row   ' wsがNothingでも続行
    ' → lastRowは0になりループが回らない、あるいはエラーが起きたまま続行

    For i = 1 To lastRow
        ws.Cells(i, 3).Value = ws.Cells(i, 1).Value + ws.Cells(i, 2).Value
        ' 型エラーが起きてもスルー → 間違った値が書き込まれる
    Next i

    ' On Error GoTo 0 もない → エラー状態のまま次の処理へ

End Sub

アンチパターン2:Err.Clearなしに複数の操作をまとめてOn Error Resume Nextで囲む

' NG:複数操作をまとめてOn Error Resume Nextで囲み、最後に一度だけチェック
' どの行でエラーが起きたかわからない
Sub BadPattern2()

    On Error Resume Next

    Dim ws  As Worksheet
    Dim rng As Range
    Dim val As Long

    Set ws  = ThisWorkbook.Sheets("集計")
    Set rng = ws.Range("A1:C100")
    val     = rng.Cells(1, 1).Value

    ' 最後に一度だけチェック → どこでエラーが起きたか不明
    If Err.Number <> 0 Then
        Debug.Print "どこかでエラーが出ました: " & Err.Number
    End If

    On Error GoTo 0

End Sub

アンチパターン3:On Error GoTo 0で解除するのを忘れる

' NG:On Error Resume Nextを解除せずにプロシージャを終了する
' 次に呼び出したプロシージャにも影響が残る場合がある
Sub BadPattern3()

    On Error Resume Next

    ' 何らかの処理
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets(1)

    ' On Error GoTo 0 がない → エラー状態が次の処理に引き継がれる可能性

    ' プロシージャ終了(エラーハンドリングが有効なまま)

End Sub

Sub CalledAfterBadPattern3()

    ' BadPattern3の後で呼ばれるプロシージャ
    ' On Error Resume Nextの効果はプロシージャを跨がないが、
    ' Err.Numberが0でない状態が残ることがある
    ' → 混乱の原因になる

End Sub

正しいOn Error Resume Nextの使い方

On Error Resume Next が本来適切に使われるのは、「エラーになることが想定されるが、失敗しても処理を続けたい1〜2行の操作」に対して、直前にオンにして直後にオフにするという局所的な使い方です。

正しい使用パターン:オブジェクトの存在確認

' ========================================
' 正しい使い方1:オブジェクトの存在確認(最も一般的な用途)
' ========================================

' シートの存在確認
Function SheetExists(sheetName As String, Optional wb As Workbook) As Boolean

    If wb Is Nothing Then Set wb = ThisWorkbook

    Dim ws As Worksheet

    ' On Error Resume Nextをこの1行だけに限定する
    On Error Resume Next
    Set ws = wb.Sheets(sheetName)
    On Error GoTo 0   ' ← 必ず直後に解除

    SheetExists = Not (ws Is Nothing)
    Set ws = Nothing

End Function

' ブックが開かれているかの確認
Function WorkbookIsOpen(wbName As String) As Boolean

    Dim wb As Workbook

    On Error Resume Next
    Set wb = Workbooks(wbName)
    On Error GoTo 0

    WorkbookIsOpen = Not (wb Is Nothing)
    Set wb = Nothing

End Function

' 使用例
Sub UseExistenceCheck()

    If SheetExists("集計") Then
        Debug.Print "集計シートが存在します"
    Else
        MsgBox "集計シートが見つかりません。作成しますか?", vbYesNo
    End If

    If WorkbookIsOpen("売上データ.xlsx") Then
        Debug.Print "売上データ.xlsxは開かれています"
    Else
        MsgBox "売上データ.xlsxを先に開いてください。", vbExclamation
    End If

End Sub

正しい使用パターン:Collectionのキー確認

' ========================================
' 正しい使い方2:CollectionやDictionaryのキー確認
' ========================================
Function CollectionKeyExists(col As Collection, key As String) As Boolean

    Dim dummy As Variant

    On Error Resume Next
    dummy = col(key)
    CollectionKeyExists = (Err.Number = 0)
    Err.Clear        ' 確認後にクリア
    On Error GoTo 0

End Function

' 使用例
Sub UseCollectionCheck()

    Dim col As New Collection
    col.Add "東京", "tokyo"
    col.Add "大阪", "osaka"

    If CollectionKeyExists(col, "tokyo") Then
        Debug.Print "キー'tokyo'の値: " & col("tokyo")
    End If

    If Not CollectionKeyExists(col, "nagoya") Then
        Debug.Print "キー'nagoya'は存在しません"
    End If

End Sub

正しい使用パターン:外部オブジェクトのインスタンス確認

' ========================================
' 正しい使い方3:外部アプリの存在確認
' ========================================
Function OutlookIsAvailable() As Boolean

    Dim obj As Object

    On Error Resume Next
    Set obj = CreateObject("Outlook.Application")
    OutlookIsAvailable = (Err.Number = 0)
    Err.Clear
    On Error GoTo 0

    If Not obj Is Nothing Then Set obj = Nothing

End Function

' 正しい使い方4:数値変換の失敗を無視して0を返す
Function TryParseDouble(val As Variant, Optional defaultVal As Double = 0) As Double

    On Error Resume Next

    Err.Clear
    TryParseDouble = CDbl(val)

    If Err.Number <> 0 Then
        TryParseDouble = defaultVal
        Err.Clear
    End If

    On Error GoTo 0

End Function

Sub UseConvert()
    Debug.Print TryParseDouble("123.45")      ' → 123.45
    Debug.Print TryParseDouble("abc", -1)     ' → -1
    Debug.Print TryParseDouble(Empty, 0)      ' → 0
End Sub

On Error Resume Nextの正しい使い方・守るべき3原則

  • 原則1:使用範囲は最小限にOn Error Resume Next はエラーが予想される1〜3行だけに限定する。有効にした直後に On Error GoTo 0 で解除する
  • 原則2:Err.Clearをチェック前に必ず呼ぶ:前のエラーが残っていると誤判定の原因になる。On Error Resume Next の直後に Err.Clear を書く習慣をつける
  • 原則3:確認後は必ず解除するOn Error Resume Next を有効にしたら、使い終わった直後に必ず On Error GoTo 0 で解除する。プロシージャ末尾まで有効なままにしない

On Error GoToによる本格的なエラーハンドリング

On Error Resume Next が「局所的な存在確認」に使うツールであるのに対して、プロシージャ全体のエラー処理には On Error GoTo ラベル を使います。こちらはエラーが発生した瞬間に指定したラベルにジャンプするため、エラーの見落としが起きません。

' ========================================
' On Error GoToによる標準的なエラーハンドリングパターン
' ========================================
Sub StandardErrorHandling()

    ' ---- 変数宣言 ----
    Dim ws      As Worksheet
    Dim lastRow As Long
    Dim i       As Long

    ' ---- 高速化設定 ----
    Application.ScreenUpdating = False
    Application.Calculation    = xlCalculationManual
    Application.EnableEvents   = False

    ' ---- エラーハンドラを登録 ----
    On Error GoTo ErrorHandler

    ' ===== メイン処理 =====
    Set ws      = ThisWorkbook.Sheets("データ入力")
    lastRow     = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

    Dim srcData As Variant
    Dim outData As Variant

    srcData = ws.Range("A2:B" & lastRow).Value
    ReDim outData(1 To UBound(srcData, 1), 1 To 1)

    For i = 1 To UBound(srcData, 1)
        outData(i, 1) = CDbl(srcData(i, 1)) * CDbl(srcData(i, 2))
    Next i

    ws.Range("C2:C" & lastRow).Value = outData
    ' =====================

    MsgBox "処理完了(" & lastRow - 1 & " 行)", vbInformation
    GoTo CleanUp

ErrorHandler:
    ' エラー情報を変数に保存(CleanUpでErrオブジェクトがリセットされる前に)
    Dim errNum  As Long
    Dim errDesc As String
    Dim errLine As String

    errNum  = Err.Number
    errDesc = Err.Description
    errLine = "処理行: " & i & " 行目"   ' ループ変数で位置を特定

    MsgBox "エラーが発生しました。" & vbCrLf & vbCrLf & _
           "番号: " & errNum & vbCrLf & _
           "内容: " & errDesc & vbCrLf & _
           errLine, vbCritical

CleanUp:
    ' ---- 設定を元に戻す(エラー時も必ず実行) ----
    Application.ScreenUpdating = True
    Application.Calculation    = xlCalculationAutomatic
    Application.EnableEvents   = True

    Set ws = Nothing

End Sub

On Error GoToとOn Error Resume Nextを組み合わせる実践パターン

' ========================================
' On Error GoToをメインに使いつつ、
' 特定の操作だけOn Error Resume Nextで囲むパターン
' ========================================
Sub CombinedErrorHandling()

    Dim ws       As Worksheet
    Dim wsOut    As Worksheet
    Dim lastRow  As Long

    Application.ScreenUpdating = False
    Application.Calculation    = xlCalculationManual

    On Error GoTo ErrorHandler   ' メインのエラーハンドラ

    ' === シート取得(存在しない場合は作成する) ===

    ' ここだけOn Error Resume Nextを局所的に使う
    On Error Resume Next
    Err.Clear
    Set wsOut = ThisWorkbook.Sheets("結果出力")
    On Error GoTo ErrorHandler   ' ← 局所的使用後、すぐにGoToハンドラに戻す

    ' シートが存在しなければ作成する
    If wsOut Is Nothing Then
        Set wsOut = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
        wsOut.Name = "結果出力"
    End If

    ' === メインの処理(エラーが起きれば ErrorHandler にジャンプ) ===
    Set ws  = ThisWorkbook.Sheets("データ入力")
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

    Dim data As Variant
    data = ws.Range("A2:C" & lastRow).Value

    wsOut.Cells.Clear
    wsOut.Range("A2").Resize(UBound(data, 1), UBound(data, 2)).Value = data

    MsgBox "処理完了", vbInformation
    GoTo CleanUp

ErrorHandler:
    MsgBox "エラー " & Err.Number & ": " & Err.Description, vbCritical

CleanUp:
    Application.ScreenUpdating = True
    Application.Calculation    = xlCalculationAutomatic

    Set ws    = Nothing
    Set wsOut = Nothing

End Sub

エラーログを自動記録する仕組みを作る

「どこでエラーが起きているか後から確認したい」「エラーが起きても処理を続けながら最後にまとめて報告したい」という場合は、エラー情報をログとして記録する仕組みを作ると便利です。

' ========================================
' エラーログを記録しながら処理を続行するパターン
' 全行を処理して最後にエラー一覧を表示する
' ========================================
Sub ProcessWithErrorLog()

    Dim ws      As Worksheet
    Dim lastRow As Long
    Dim i       As Long
    Dim errLog  As String   ' エラーログを文字列で蓄積
    Dim errCount As Long

    Set ws      = ThisWorkbook.Sheets(1)
    lastRow     = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
    errLog      = ""
    errCount    = 0

    Application.ScreenUpdating = False
    Application.Calculation    = xlCalculationManual

    For i = 2 To lastRow

        On Error Resume Next
        Err.Clear

        ' 処理(型変換を含む演算)
        Dim val1 As Double, val2 As Double
        val1 = CDbl(ws.Cells(i, 1).Value)
        val2 = CDbl(ws.Cells(i, 2).Value)

        If Err.Number = 0 Then
            ws.Cells(i, 3).Value = val1 * val2
        Else
            ' エラーをログに追記
            errCount = errCount + 1
            errLog   = errLog & i & "行目: " & ws.Cells(i, 1).Value & _
                       " / " & ws.Cells(i, 2).Value & _
                       "  → エラー" & Err.Number & ":" & Err.Description & vbCrLf
            ws.Cells(i, 3).Value = ""   ' エラー行は空欄
            Err.Clear
        End If

        On Error GoTo 0

    Next i

    Application.ScreenUpdating = True
    Application.Calculation    = xlCalculationAutomatic

    ' 最後にエラーをまとめて報告
    If errCount > 0 Then
        MsgBox errCount & " 件のエラーが発生しました。" & vbCrLf & vbCrLf & errLog, _
               vbExclamation, "処理完了(エラーあり)"
    Else
        MsgBox "処理完了(エラーなし)", vbInformation
    End If

    Set ws = Nothing

End Sub
' ========================================
' エラーをシートのログシートに記録する恒久的な仕組み
' ========================================
Sub LogErrorToSheet(errNum As Long, errDesc As String, _
                    location As String, Optional detail As String = "")

    Dim wsLog As Worksheet

    ' ログシートの取得または作成
    On Error Resume Next
    Set wsLog = ThisWorkbook.Sheets("エラーログ")
    On Error GoTo 0

    If wsLog Is Nothing Then
        Set wsLog = ThisWorkbook.Sheets.Add( _
                        After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
        wsLog.Name = "エラーログ"
        wsLog.Range("A1:E1").Value = Array("日時", "エラー番号", "説明", "発生箇所", "詳細")
        wsLog.Range("A1:E1").Font.Bold = True
    End If

    ' 最終行の次に追記
    Dim nextRow As Long
    nextRow = wsLog.Cells(wsLog.Rows.Count, 1).End(xlUp).Row + 1

    wsLog.Cells(nextRow, 1).Value = Format(Now(), "yyyy/mm/dd HH:mm:ss")
    wsLog.Cells(nextRow, 2).Value = errNum
    wsLog.Cells(nextRow, 3).Value = errDesc
    wsLog.Cells(nextRow, 4).Value = location
    wsLog.Cells(nextRow, 5).Value = detail

    wsLog.Columns("A:E").AutoFit
    Set wsLog = Nothing

End Sub

' 使用例
Sub ProcessWithSheetLog()

    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets(1)

    Dim i As Long
    For i = 2 To 1000

        On Error Resume Next
        Err.Clear

        Dim result As Double
        result = CDbl(ws.Cells(i, 1).Value) / CDbl(ws.Cells(i, 2).Value)

        If Err.Number <> 0 Then
            LogErrorToSheet Err.Number, Err.Description, _
                           "ProcessWithSheetLog", _
                           i & "行目: " & ws.Cells(i, 1).Value & " / " & ws.Cells(i, 2).Value
            result = 0
            Err.Clear
        End If

        On Error GoTo 0
        ws.Cells(i, 3).Value = result

    Next i

    Set ws = Nothing
    MsgBox "処理完了。エラーログシートを確認してください。", vbInformation

End Sub

On Error Resume Next地獄から脱出するデバッグ手順

既存のコードが On Error Resume Next だらけで、どこで何が起きているか完全にわからなくなっている場合の、体系的な脱出手順を解説します。

ステップ1:コード全体のOn Error Resume Nextをすべてコメントアウトする

まずすべての On Error Resume Next をコメントアウトしてから実行し、どのエラーが最初に止まるかを確認します。VBAエディタの「編集」→「置換」(Ctrl+H)で一括検索・コメントアウトできます。

' 一括コメントアウトの検索・置換設定
' 検索する文字列:  On Error Resume Next
' 置換後の文字列:  'On Error Resume Next
' → これで全件をコメントアウトできる

ステップ2:最初に止まったエラーを1つずつ修正する

エラーが止まった行を確認し、原因を修正します。修正できたら再実行して次のエラーに進みます。この作業を繰り返してエラーをゼロにしていきます。

ステップ3:本当にOn Error Resume Nextが必要な箇所だけ復活させる

「存在確認」など、意図的にエラーを利用している箇所にのみ On Error Resume Next を復活させます。その際、前述の「3原則」を守って局所的に使います。

ステップ4:残りのエラー処理をOn Error GoToに切り替える

' ========================================
' On Error Resume Nextをリファクタリングする
' 「あとで直す」と思ってそのままにしがちな部分を
' 正しいOn Error GoToに書き換えるテンプレート
' ========================================

' ---- Before(アンチパターン)----
Sub BeforeRefactor()

    On Error Resume Next

    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("データ")

    Dim lastRow As Long
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

    Dim i As Long
    For i = 1 To lastRow
        ws.Cells(i, 2).Value = CDbl(ws.Cells(i, 1).Value) * 1.1
    Next i

End Sub

' ---- After(正しいパターン)----
Sub AfterRefactor()

    Dim ws      As Worksheet
    Dim lastRow As Long
    Dim i       As Long

    Application.ScreenUpdating = False
    Application.Calculation    = xlCalculationManual

    On Error GoTo ErrorHandler

    ' シートの存在確認は局所的なOn Error Resume Nextを使う
    On Error Resume Next
    Err.Clear
    Set ws = ThisWorkbook.Sheets("データ")
    On Error GoTo ErrorHandler   ' 即座に通常のエラーハンドラに戻す

    If ws Is Nothing Then
        MsgBox "「データ」シートが見つかりません。", vbCritical
        GoTo CleanUp
    End If

    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

    Dim srcData As Variant
    Dim outData As Variant
    srcData = ws.Range("A1:A" & lastRow).Value
    ReDim outData(1 To lastRow, 1 To 1)

    For i = 1 To lastRow
        If IsNumeric(srcData(i, 1)) Then
            outData(i, 1) = CDbl(srcData(i, 1)) * 1.1
        Else
            outData(i, 1) = 0
        End If
    Next i

    ws.Range("B1:B" & lastRow).Value = outData
    MsgBox "完了(" & lastRow & "行)", vbInformation
    GoTo CleanUp

ErrorHandler:
    MsgBox "エラー " & Err.Number & ": " & Err.Description, vbCritical

CleanUp:
    Application.ScreenUpdating = True
    Application.Calculation    = xlCalculationAutomatic
    Set ws = Nothing

End Sub

プロジェクト全体のエラーハンドリング設計

個別のプロシージャのエラー処理を正しく書くだけでなく、プロジェクト全体で一貫したエラーハンドリングの設計をすることで、デバッグと保守が格段に楽になります。

共通エラーハンドラ関数の実装

' ========================================
' プロジェクト共通エラーハンドラ
' 標準モジュール(modErrorHandler)に記述する
' ========================================
Option Explicit

' エラー処理の共通関数
Public Sub HandleError( _
    ByVal errNum   As Long, _
    ByVal errDesc  As String, _
    ByVal procName As String, _
    Optional ByVal detail   As String = "", _
    Optional ByVal showMsg  As Boolean = True, _
    Optional ByVal logToSheet As Boolean = True)

    Dim fullMsg As String
    fullMsg = "【" & procName & "】" & vbCrLf & _
              "エラー番号: " & errNum & vbCrLf & _
              "内 容: " & errDesc

    If Len(detail) > 0 Then
        fullMsg = fullMsg & vbCrLf & "詳 細: " & detail
    End If

    ' ログシートへの記録
    If logToSheet Then
        LogErrorToSheet errNum, errDesc, procName, detail
    End If

    ' デバッグウィンドウへの出力
    Debug.Print Format(Now(), "HH:mm:ss") & " ERROR - " & fullMsg

    ' メッセージボックス表示
    If showMsg Then
        MsgBox fullMsg, vbCritical, "エラーが発生しました"
    End If

End Sub

' エラー番号の種類別メッセージ
Public Function GetFriendlyErrorMsg(errNum As Long) As String

    Select Case errNum
        Case 9   : GetFriendlyErrorMsg = "シート名や配列の添字が正しくありません。名前を確認してください。"
        Case 11  : GetFriendlyErrorMsg = "ゼロ除算が発生しました。分母がゼロになっていないか確認してください。"
        Case 13  : GetFriendlyErrorMsg = "データの型が一致しません。数値が必要な場所に文字列が入っていないか確認してください。"
        Case 53  : GetFriendlyErrorMsg = "ファイルが見つかりません。パスとファイル名を確認してください。"
        Case 75  : GetFriendlyErrorMsg = "フォルダのパスが無効です。フォルダの存在を確認してください。"
        Case 91  : GetFriendlyErrorMsg = "オブジェクトが設定されていません。Setで代入してから使用してください。"
        Case 438 : GetFriendlyErrorMsg = "プロパティまたはメソッドが存在しません。名前のスペルを確認してください。"
        Case 1004: GetFriendlyErrorMsg = "Excelオブジェクトへの操作が失敗しました。シートの保護やセル範囲を確認してください。"
        Case Else: GetFriendlyErrorMsg = "予期しないエラーです。"
    End Select

End Function
' ========================================
' 共通エラーハンドラを使ったプロシージャのテンプレート
' ========================================
Sub ProcessTemplate()

    Dim ws      As Worksheet
    Dim lastRow As Long
    Dim i       As Long

    Application.ScreenUpdating = False
    Application.Calculation    = xlCalculationManual
    Application.EnableEvents   = False

    On Error GoTo ErrorHandler

    ' ===== メイン処理 =====
    Set ws      = ThisWorkbook.Sheets("データ")
    lastRow     = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

    Dim srcData As Variant
    srcData = ws.Range("A2:B" & lastRow).Value

    Dim outData As Variant
    ReDim outData(1 To UBound(srcData, 1), 1 To 1)

    For i = 1 To UBound(srcData, 1)
        outData(i, 1) = CDbl(srcData(i, 1)) + CDbl(srcData(i, 2))
    Next i

    ws.Range("C2:C" & lastRow).Value = outData
    ' =====================

    MsgBox "処理完了", vbInformation
    GoTo CleanUp

ErrorHandler:
    HandleError Err.Number, Err.Description, "ProcessTemplate", _
                "処理行: " & i & " 行目"

CleanUp:
    Application.ScreenUpdating = True
    Application.Calculation    = xlCalculationAutomatic
    Application.EnableEvents   = True

    Set ws = Nothing

End Sub

On Error Resume Next チェックリスト

既存コードを見直すとき、または新しいコードを書くとき、以下のリストで確認してください。

  • On Error Resume Nextプロシージャ全体を囲んでいないか(先頭に書いてそのまま末尾まで有効な状態になっていないか)
  • On Error Resume Next の後に必ず Err.Clear を呼んでいるか
  • エラーチェックの直前に Err.Clear を入れているか(前のエラーが残っていないか)
  • エラーが発生したかどうかを If Err.Number <> 0 Then で確認しているか
  • 確認後に On Error GoTo 0 または On Error GoTo ErrorHandler で解除しているか
  • On Error Resume Next の有効範囲が 1〜3行程度の必要最小限になっているか
  • プロシージャ全体のエラー処理に On Error GoTo ErrorHandler を使っているか
  • エラーハンドラ内で Err.NumberErr.Description を変数に保存してから処理しているか(CleanUpでリセットされる前に)
  • エラー発生時も CleanUp ラベルで設定が元に戻るよう設計されているか
  • エラーをログ記録する仕組みがあるか(シートログ・イミディエイトウィンドウ出力
  • 「どこでエラーが出ているか特定できない」と感じたら、すぐに On Error Resume Next をコメントアウトしてF8ステップ実行する習慣があるか

まとめ

On Error Resume Next は「エラーを無視する命令」ではなく、「エラーが起きても次の行に進む命令」です。使い方を誤るとエラーが積み重なって原因の特定が困難になりますが、正しい使い方を理解すれば強力なツールになります。本記事の要点をまとめます。

  • エラー箇所の特定On Error Resume Next をコメントアウトしてF8ステップ実行するのが最速。または各行の後にErr.Numberチェックを挟んでトレースする
  • Err.Clearは必須On Error Resume Next の直後と各エラーチェックの直前に Err.Clear を呼ぶ。これを忘れると前のエラーが残り続ける
  • 使用範囲は最小限On Error Resume Next は「存在確認」などエラーが予想される1〜3行だけに限定し、直後に On Error GoTo 0 または On Error GoTo ErrorHandler で解除する
  • プロシージャ全体はOn Error GoTo:メインの処理全体のエラーハンドリングには On Error GoTo ErrorHandler を使う。これならエラーが起きた瞬間に確実にハンドラに飛ぶ
  • エラーログ:ループ内でエラーをスキップしながら処理を続けたい場合は、エラー情報を文字列またはログシートに蓄積して最後にまとめて報告する設計にする
  • 共通エラーハンドラ:プロジェクト全体で HandleError のような共通関数を用意してエラー記録・表示を一元管理すると保守性が大きく向上する

On Error Resume Next を書いたらどこでエラーが出てるかわからなくなった」という状況は、この記事の手順に従えば必ず原因を特定できます。そして再発防止のために、On Error Resume Next の使用箇所を局所的に限定し、プロシージャ全体のエラー管理を On Error GoTo に置き換えることが根本的な解決策です。