🚀The world's best VBA AI has evolved. ExcelMaster is now an autonomous Agent.Read more →
Back to Blog

VBA エラー処理(Excel)— 信頼できるマクロが必ず使うたった一つの型

|

VBA エラー処理(Excel)— 信頼できるマクロが必ず使うたった一つの型

検証環境: Excel 365 v2509 · Excel 2021 · Excel 2019 · 最終確認 2026-06-08

要点 — VBA に try/catch はありません。信頼できるエラー処理は、重要なプロシージャすべてで使い回す一つの「かたち」です。冒頭でハンドラーを武装し、処理を実行し、すべて の出口を——成功でも失敗でも——一つの後始末ポイントに通してから抜ける。次のテンプレートをコピーしてください。

Sub ProcessReport()
    On Error GoTo ErrHandler
    Application.ScreenUpdating = False        ' 後で必ず元に戻すべき状態

    Dim ws As Worksheet
    Set ws = Sheets("Data")                   ' ここでエラーが起きれば ErrHandler へ飛ぶ
    ws.Range("A1:A1000").Value = 0

CleanExit:                                    ' すべての経路が通る唯一の出口
    Application.ScreenUpdating = True         ' エラーの有無にかかわらず必ず復元
    Exit Sub

ErrHandler:
    MsgBox "ProcessReport に失敗しました: " & Err.Description
    Resume CleanExit                          ' 後始末してから抜ける
End Sub

この構造——On Error GoTo / 処理 / CleanExit: / Exit Sub / ErrHandler: / Resume CleanExit——が規律のすべてです。本記事の残りは、各行が なぜ そこにあるのかの解説です。

メンタルモデル:VBA が用意してくれない try/finally を、自分の手で組み立てる

Python や C# には try / except / finally があります。VBA にはどれもありません。代わりに VBA が与えるのは 3 つの部品——On Error GoTo Label、ラベル、Resume——と、それらを自分で同等の構造へ組み上げることへの期待 です。

try/finally に対応づけると、もう恣意的には見えなくなります。

try/catch/finally VBA での対応
try { On Error GoTo ErrHandler
catch (e) { ErrHandler: ブロック(Err を読む)
finally { 両経路が到達する CleanExit: ブロック
throw Err.Raise

このテンプレートを「手書きの try/finally」と見た瞬間、各行の置き場所は丸暗記ではなく自明になります。譲れない核心は、成功経路と失敗経路の両方が通る、たった一つの後始末ラベル です。

たった一つのルール:Resume がハンドラー後の行き先を決める ——選択を誤るとループするか漏れる

ハンドラーの中で処理を続ける方法は 3 つあり、誤った一つを選ぶことが VBA エラー処理の典型的なバグです。

Resume              ' 失敗したまさにその行を再実行 ——原因を直した場合だけ安全
Resume Next         ' 失敗した行をスキップし、その次の行から続行
Resume CleanExit    ' ラベルへジャンプ ——通常はあなたの唯一の後始末出口
  • Resume は問題の行をやり直します。使ってよいのは、ハンドラーが 何かを変えた おかげで再試行が成功できるときだけです(無かったフォルダーを作った、閉じていたブックを開いた)。盲目的に使うと 無限ループ になります——失敗、処理、再試行、失敗、を永遠に繰り返す。
  • Resume Next は失敗を飛ばして続行します。「記録して先へ進む」には適しますが、飛ばした行が肝心な処理だったときは危険です。
  • Resume Label が主力です。制御をあなたの CleanExit: へ送り、プロシージャが終わる前に必ず後始末が走るようにします。

ResumeExit も無いままハンドラーの末尾を抜けてしまうと、エラーが実質的にマクロを止めます——だから出口は必ず明示的に決めましょう。

みんなが飛ばす肝心な部分:たった一つの後始末ポイントが、あなたのファイルを救う

小さなバグをサポートチケットに変える失敗モードがこれです。マクロが速度のためにグローバルな Excel の状態を設定します。

Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual

…そして、それらを元に戻す にエラーで落ちる。すると Excel は固まったまま再計算もせず、利用者は——あなたのマクロがとっくに終わった後になって——Excel 自体が壊れたと思い込みます。同じ筋書きは、開いたままのファイルハンドル(ロックされたまま)、解放されない Set オブジェクト、保護を外したままのシートにも当てはまります。

直し方は、丁寧にコードを書くことではなく、構造です。「必ず走るべき」行をすべて、正常経路とハンドラーの両方が到達する一つのラベル に置き、ハンドラーをそこへ Resume させます。

Sub ImportFile()
    Dim ff As Integer
    On Error GoTo ErrHandler
    Application.ScreenUpdating = False
    ff = FreeFile
    Open "C:\Data\in.csv" For Input As #ff
    ' ... ファイルを読む。途中で吹き飛ぶかもしれない ...

CleanExit:
    If ff <> 0 Then Close #ff             ' ファイルは必ず閉じる
    Application.ScreenUpdating = True      ' UI は必ず復元
    Exit Sub
ErrHandler:
    MsgBox "取り込みに失敗しました: " & Err.Description
    Resume CleanExit                      ' 失敗経路も後始末を通る
End Sub

成功すると CleanExit に落ち込みます。失敗すると ErrHandler へ飛び、それから Resume CleanExitどちらの 経路もファイルを閉じ、UI を復元します。これがあなたの finally です。

エラーを呼び出し側へ持ち上げる(再送出)

低レベルの補助プロシージャは、たいてい「アプリ全体がどう反応するか」を決めるべきではありません——自分のリソースだけ後始末し、判断は呼び出し側に委ねるべきです。エラーを取り込み、状態を復元してから、再送出します。

Private Sub LoadData()
    On Error GoTo ErrHandler
    ' ... 失敗するかもしれない処理 ...
    Exit Sub
ErrHandler:
    Dim n As Long, d As String, s As String
    n = Err.Number: d = Err.Description: s = Err.Source   ' 後始末が Err を消す前に保存
    ' (ここでローカルな後始末)
    Err.Raise n, s, d                                     ' 呼び出し側のハンドラーへ再送出
End Sub

先に Err の値を保存してください——後始末のステートメントが、あなたの足元で Err をクリアしてしまいます。そのうえで Err.Raise が、元のエラーを LoadData を呼んだ相手へ持ち上げ、最上位のハンドラーが一度だけ記録し、利用者には一つのきれいなメッセージを見せられます。

集約型のロギングハンドラー

出荷するものなら何であれ、エラーは利用者が MsgBox をスクリーンショットしてくれると信じるのではなく、後で読める場所に記録しましょう。

ErrHandler:
    LogError "ProcessReport", Err.Number, Err.Description
    Resume CleanExit
End Sub

Private Sub LogError(proc As String, num As Long, desc As String)
    Dim ff As Integer: ff = FreeFile
    Open ThisWorkbook.Path & "\error_log.txt" For Append As #ff
    Print #ff, Now & " | " & proc & " | " & num & " | " & desc
    Close #ff
End Sub

On Error Resume Next が正しい道具であるとき

すべての場面にハンドラーが要るわけではありません。失敗が 見込まれ、かつ無害 な一行——オブジェクトが存在するか探る——には、狭い Resume Next のほうがラベルよりすっきりします。ただし Err.Number を確認し、On Error GoTo 0 で解除する場合に限ります。

On Error Resume Next
Set ws = Sheets("Optional")
On Error GoTo 0
If ws Is Nothing Then Set ws = Sheets.Add   ' 存在しなかった → 作成する

これが安全なときと無謀なときの全体像は On Error: Resume Next と GoTo にまとめてあります。

踏み込んだ判断:成熟度は「ハンドラーをいくつ書いたか」ではない ——それらが収束するかどうかだ

すべてのプロシージャに On Error GoTo を振りまいても、なお脆いマクロは書けます。エラー処理の成熟を示すサインは、もっと狭く、もっと厳しいものです。すべてのプロシージャに、あらゆる経路が通る一つの出口があり、そこで後始末が起きるか? 答えが「いいえ」なら、プロシージャ途中のたった一つのエラーが ScreenUpdating を OFF のまま、計算エンジンを手動のまま、ファイルをロックしたまま残し——利用者はあなたのコードではなく Excel を責めます。

だから私の「このマクロは本番で使える」の基準は、ハンドラーの有無ではありません。基準はこうです。どの行でもいい、それが例外を投げたと想像してみる——それでもファイルは閉じられ、UI は復元されるか? 「はい」なら、あなたはこの型を体得しています。「いいえ」なら、On Error ステートメントをいくら増やしても救われません。救うのは一つの CleanExit: です。

よくあるエラー処理の誤り(と直し方)

症状 原因 直し方
マクロのエラー後に Excel が「固まる」 ScreenUpdatingCalculation を復元していない ハンドラーが Resume する CleanExit: で復元する
ファイルがロックされたまま/「すでに開いています」 失敗経路で CloseSet …= Nothing を飛ばした 両経路が到達する唯一の出口に後始末を置く
無限ループ 原因未修正の行を Resume がやり直している 裸の Resume ではなく Resume NextResume Label を使う
呼び出し側が失敗を知らないまま 補助が MsgBox でエラーを飲み込んだ Err を保存し、後始末し、Err.Raise で再送出する
ログがエラー 0 を表示 後始末が消した後で Err を読んだ 後始末の前に Err.NumberDescription をコピーする
成功時にも失敗メッセージが出る ErrHandler: の前に Exit Sub が無い ラベルの前に Exit Sub を置く(または CleanExit に落ち込ませる)

後始末が本来の処理を上回るなら ——仕事を言葉で伝えるだけでいい

あのテンプレートを見返してください。本来のタスクは 2 行。ハンドラー、後始末ラベル、Resume、ロギング、再送出が残りの 20 行です。実務のパイプラインでは、その比率はさらに悪化します。ExcelMaster Agent なら、仕事——「これらの CSV を取り込み、注文 ID で突き合わせ、不一致に印を付けて、経理にメールする」——を自然な日本語で説明するだけで、生成された Python が失敗を処理し、状態を復元し、何かに触れる前にブックをバックアップします。On ErrorResume も、固まった Excel もありません。無料で試す →

関連記事

よくある質問

VBA に try/catch はありますか? いいえ。VBA に try/catch/finally はありません。同等の仕組みを On Error GoTo Label、ハンドラーのラベル、一つの後始末ラベル、そして Resume から自分で組み立てます。標準のテンプレートは、冒頭でハンドラーを武装し、すべての出口を一つの後始末ポイントに通してから抜けます。

VBA で最良のエラー処理パターンは何ですか? On Error GoTo ErrHandler で武装し、処理を実行し、CleanExit: ラベルで状態(ScreenUpdating、開いたファイル、オブジェクト)を復元して Exit Sub します。ErrHandler: ブロックは Err を読み、必要なら記録し、Resume CleanExit します。成功も失敗も同じ後始末を通る——これが要です。

Resume・Resume Next・Resume Label の違いは何ですか? Resume は失敗した行を再実行します(ハンドラーが原因を直した場合だけ安全。さもないと無限ループ)。Resume Next は失敗した行をスキップして続行します。Resume Label はラベルへジャンプします——通常はあなたの唯一の後始末出口で、後始末が必ず走るようにします。

VBA でエラー後に後始末を必ず実行させるには? 「必ず走るべき」ステートメントをすべて一つのラベル(例:CleanExit:)に置き、正常経路がそこに落ち込む位置に配置したうえで、エラーハンドラーを Resume CleanExit させます。すると両経路が後始末を実行します。これが VBA における finally の代替です。

補助プロシージャから呼び出し側へエラーを渡すには? 補助側のハンドラーで Err.NumberErr.DescriptionErr.Source を変数へ保存し、ローカルな後始末を済ませてから、保存した値で Err.Raise を呼びます。エラーは呼び出し側の On Error ハンドラーへ伝播し、そこで一度だけ記録して利用者に一つのメッセージを見せられます。