検証環境: 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:へ送り、プロシージャが終わる前に必ず後始末が走るようにします。
Resume も Exit も無いままハンドラーの末尾を抜けてしまうと、エラーが実質的にマクロを止めます——だから出口は必ず明示的に決めましょう。
みんなが飛ばす肝心な部分:たった一つの後始末ポイントが、あなたのファイルを救う
小さなバグをサポートチケットに変える失敗モードがこれです。マクロが速度のためにグローバルな 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 が「固まる」 | ScreenUpdating/Calculation を復元していない |
ハンドラーが Resume する CleanExit: で復元する |
| ファイルがロックされたまま/「すでに開いています」 | 失敗経路で Close/Set …= Nothing を飛ばした |
両経路が到達する唯一の出口に後始末を置く |
| 無限ループ | 原因未修正の行を Resume がやり直している |
裸の Resume ではなく Resume Next か Resume Label を使う |
| 呼び出し側が失敗を知らないまま | 補助が MsgBox でエラーを飲み込んだ | Err を保存し、後始末し、Err.Raise で再送出する |
| ログがエラー 0 を表示 | 後始末が消した後で Err を読んだ |
後始末の前に Err.Number/Description をコピーする |
| 成功時にも失敗メッセージが出る | ErrHandler: の前に Exit Sub が無い |
ラベルの前に Exit Sub を置く(または CleanExit に落ち込ませる) |
後始末が本来の処理を上回るなら ——仕事を言葉で伝えるだけでいい
あのテンプレートを見返してください。本来のタスクは 2 行。ハンドラー、後始末ラベル、Resume、ロギング、再送出が残りの 20 行です。実務のパイプラインでは、その比率はさらに悪化します。ExcelMaster Agent なら、仕事——「これらの CSV を取り込み、注文 ID で突き合わせ、不一致に印を付けて、経理にメールする」——を自然な日本語で説明するだけで、生成された Python が失敗を処理し、状態を復元し、何かに触れる前にブックをバックアップします。On Error も Resume も、固まった Excel もありません。無料で試す →
関連記事
- VBA の On Error とは — Resume Next と GoTo、マクロがバグを隠す理由
- VBA の Err オブジェクト — Number・Description と自前エラーの送出
- VBA の Sub とは — プロシージャの呼び出し方と「マクロの正体」
- VBA For ループ — 実務で使う 8 つの例
よくある質問
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.Number、Err.Description、Err.Source を変数へ保存し、ローカルな後始末を済ませてから、保存した値で Err.Raise を呼びます。エラーは呼び出し側の On Error ハンドラーへ伝播し、そこで一度だけ記録して利用者に一つのメッセージを見せられます。
