「実行時エラー ‘1004’」のダイアログが出て、Excel のマクロが途中で止まる。慌てて On Error Resume Next を1行貼ったら、今度は何も処理されない空振りマクロになった。VBA でエラー処理を覚えるとき、この2つはほぼ全員が通る失敗です。
厄介なのは、エラー処理(エラーハンドリング)を書いた「後」の状態なんです。何も書いていないマクロなら「まだ守っていない」と自覚できます。
でも一度書いてしまうと、守れているつもりで配布できてしまいますよね。事故に気づくのが数ヶ月遅れることもあります。
落ちた後の Excel が変な状態で残るケースも多いです。画面が更新されない、自動計算が手動のまま、シートを触ってもイベントが動かない。原因はエラーではなく、後始末を書いていないことにあります。
この記事では、VBA のエラーハンドリングを2本立てで整理しました。前半は On Error の書き分けを決定表1つに圧縮。
後半は「書いたのに効かない4つの落とし穴」と、落とさず終わらせる実務パターン5選を紹介します。そのままコピーして自分のマクロに貼れる形にしてありますよ。
VBAのOn Errorは3つだけ|1分で決まる書き分け決定表
冒頭でも触れたとおり、VBA のエラー処理は「エラーハンドリング」とも呼ばれます。仰々しい響きですが、覚える構文は3つだけ。文法はここで打ち止めにして、記事の主役である落とし穴と実務パターンへ進みましょう。
On Error GoTo / Resume Next / GoTo 0 の役割を1行ずつ
| 構文 | やること |
|---|---|
On Error GoTo ラベル | エラーが起きたら、指定したラベルの行へ処理を飛ばす |
On Error Resume Next | エラーが起きた行を飛ばして、次の行から実行を続ける |
On Error GoTo 0 | いま有効になっているエラー処理を無効に戻す |
ラベルというのは ErrHandler: のように、行の先頭に名前とコロンを書いた目印のこと。ジャンプ先の住所だと思ってください。ラベルから下に続く、エラー時だけ通る処理のかたまりが「エラーハンドラ」です。
On Error GoTo 0 の 0 は、行番号でもラベル名でもありません。「いま有効なエラー処理を無効に戻す」ための専用の書き方です。コードの中に 0: というラベルを用意する必要はありませんよ。
なお、これらが効く相手は実行時エラー(マクロを動かしている最中に発生するエラー)だけ。実行前の文法チェックで出るコンパイルエラーや、動くけれど結果が違う論理エラーは対象外です。
【決定表】止める・続ける・そもそも起こさせない の3択で選ぶ
やりたいことを3択に落とすと、選ぶ構文は自動的に決まります。
| やりたいこと | 使う構文 | 必ずセットで書くもの |
|---|---|---|
| エラーが出たら処理を止めて、原因を知らせたい | On Error GoTo ラベル | Exit Sub とラベル位置の処理 |
| 失敗しても構わない処理を、1〜2行だけ通したい | On Error Resume Next | 直後の On Error GoTo 0 と結果判定 |
| そもそもエラーを起こさせたくない | 事前チェック(Dir / IsNumeric / Is Nothing) | Option Explicit などの予防的な書き方 |
迷ったら3行目を先に検討してください。ファイルの有無やセルの中身は、事前にチェックすれば大半が弾けます。エラーを起こしてから拾うより、起こさせないほうがコードは短くなるんです。
変数の打ち間違いを予防するなら、【VBA】Option Explicitで変数の宣言を強制する方法もあわせてどうぞ。
Exit Sub とセットにする最小テンプレート
On Error GoTo を使うときの最小形がこれです。3つのブロックの並び順まで含めて、まるごと覚えてしまってください。
Sub SampleTemplate()
On Error GoTo ErrHandler
'--- ① 通常の処理をここに書く ---
ThisWorkbook.Worksheets("Sheet1").Range("A1").Value = "OK"
'--- ② 正常時はここで抜ける(これが無いと③に流れ込む) ---
Exit Sub
ErrHandler:
'--- ③ エラーが起きたときだけ通う場所 ---
MsgBox "処理を中断しました" & vbCrLf & _
"[" & Err.Number & "] " & Err.Description
End Sub
ポイントは②の Exit Sub。これが無いと、正常に終わったときも③のメッセージが出てしまいます。理由は【落とし穴2】で詳しく見ていきましょう。
ハンドラの中では、Resume 系の書き方で「どこから再開するか」も選べます。
| 書き方 | 再開する場所 | 主な用途 |
|---|---|---|
Exit Sub | プロシージャを抜ける | エラー時は中止して終わらせる |
Resume | エラーが起きた行 | 原因を直してもう一度試す |
Resume Next | エラーが起きた行の次 | その行だけ飛ばして続ける |
Resume ラベル | 指定したラベル | 後始末の処理へ合流させる |
プロシージャとは Sub 〜 End Sub で囲まれたひとかたまりの処理のこと。この単位が、後で出てくるスコープの話につながります。
Err.Number・Err.Description で「何が起きたか」を受け取る
エラーの中身は Err オブジェクトから取り出します。ハンドラの中で参照するのが基本の使い方です。
Sub ShowErrorInfo()
On Error GoTo ErrHandler
Dim n As Long
n = CLng("abc") '数値に変換できずエラーになる行
Exit Sub
ErrHandler:
MsgBox "番号: " & Err.Number & vbCrLf & _
"内容: " & Err.Description & vbCrLf & _
"発生元: " & Err.Source
End Sub
Err.Number がエラー番号、Err.Description が説明文です。Err.Source には発生元の名前が入ります。3つ並べて表示しておくと、利用者から状況を聞き取るときに話が早くなりますよ。
ひとつ注意点があります。Err が持っているのは直近1件の情報だけ。次のエラーが起きたり別のプロシージャを呼んだりすると上書きされます。
必要な値は変数に控えておきましょう。この作法は、後で紹介する【パターン5】のログ出力でも使います。
番号ごとの意味と直し方(実行時エラー13・1004・9・91・438など)は本記事では扱いません。姉妹記事のVBAマクロのエラー解決ガイドにまとめてあります。
NOTE
コードを書く場所は VBE(Visual Basic Editor)です。Excel で
Alt+F11を押して開き、「挿入 → 標準モジュール」を選んで貼り付けてください。各ウィンドウの役割はVBEの画面の見方を図解で解説にまとめています。保存はマクロ有効ブック(.xlsm)で。.xlsxのまま保存するとコードが消えてしまいます。
エラー処理を書いたのに効かない・空振りする4つの落とし穴
ここからが本題です。On Error は書いた瞬間に万能になる魔法ではありません。書き方や環境しだいで、効かなかったり、かえって被害を広げたりします。
現場で繰り返し見かける原因を4つに絞りました。「症状 → 確認 → 対処」の順に並べてあるので、いま困っている症状から読んでも大丈夫ですよ。
なお、エラーがどの行で起きているか特定できていない段階なら、先に原因の場所を突き止めましょう。手順はExcelマクロのVBAデバッグ方法で解説しています。
【落とし穴1】VBEの「エラー トラップ」設定が On Error より優先されることがある
まず疑ってほしいのが、コードではなく VBE 側の設定です。
症状: 同じコードなのに、自分の PC だけエラーの行で止まる。同僚の環境では On Error GoTo がちゃんと効いている。この「環境によって挙動が違う」というパターンが特徴です。
確認: VBE のメニューから「ツール → オプション」を開きます。「全般」タブに「エラー トラップ」という設定があるので、そこがどれになっているか見てください。この設定の選び方によっては、On Error を書いていてもエラーの行で中断することがあります。
対処: 設定を切り替えて同じマクロをもう一度実行し、挙動が変わるか比べてみましょう。変わるなら原因はコードではなく設定側です。自分で変えた覚えがなくても、過去のデバッグ作業の名残で切り替わっている場合があります。
対応バージョン: 本記事は Excel for Windows のデスクトップ版(Microsoft 365 / 2021 / 2019)を前提にしています。VBE の「ツール → オプション」にある項目名やタブ構成は、Excel のバージョンや言語環境で表示が異なることがあります。Mac 版の VBE はメニュー構成が異なり、Excel for the web では VBA 自体が動作しません。
そもそもマクロを実行しようとしてもブロックされる場合は、この設定とは別の問題です。Excelマクロが実行できない・ブロックされる5つの原因と対処法を先に確認してみてください。
【落とし穴2】Exit Sub 忘れで、正常時までエラーメッセージが出る
こちらは逆に、エラーが起きていないのにエラー扱いされるパターンです。
Sub BadExample_NoExitSub()
On Error GoTo ErrHandler
ThisWorkbook.Worksheets("Sheet1").Range("A1").Value = "OK"
'--- ここに Exit Sub が無い ---
ErrHandler:
MsgBox "エラー: " & Err.Description '正常時にも表示されてしまう
End Sub
VBA はラベルを「壁」として扱いません。上から順に実行していき、ErrHandler: の行もただの目印として素通りします。正常に処理が終わっても、そのまま下のハンドラへ流れ込んでしまうわけです。
このとき Err.Description は空文字列なので、「エラー: 」という中身の無いメッセージだけが出ます。配布先から「よく分からないエラーが出る」と連絡が来る典型例。実際にはエラーは起きていません。
WARNING
On Error GoTo ラベルを書いたら、ハンドラの直前に必ずExit Subを置いてください。Functionの中ならExit Functionです。この1行の有無で、正常時の挙動がまるごと変わります。
【落とし穴3】On Error GoTo 0 の解除漏れと「ハンドラ処理中は二度目を拾えない」問題
この落とし穴は2つの症状に分かれます。どちらも「書いたのに守られていない」状態を作ります。
解除漏れは、On Error Resume Next を張ったまま先へ進んでしまうケースです。
Sub Trap_GoTo0Missing()
Dim ws As Worksheet
On Error Resume Next
Set ws = ThisWorkbook.Worksheets("売上データ")
'--- ここに On Error GoTo 0 が無い ---
ws.Range("A1").Value = 100 'ws が Nothing でもダイアログが出ない
ThisWorkbook.Save '保存に失敗しても気づけない
End Sub
シートの存在チェックのつもりで書いた1行が、Sub の最後まで効き続けています。以降はどこで何が失敗しても無言で通過。「エラーが出ないから動いている」と思い込んだまま、空のシートを出力し続けるマクロが出来上がります。
対処はシンプルで、試したい処理の直後に On Error GoTo 0 を書くだけ。囲む範囲は1〜2行に閉じてくださいね。
もうひとつは、ハンドラの処理中に起きた二度目のエラーを拾えない問題です。
Sub Trap_HandlerIsOneShot()
On Error GoTo ErrHandler
Worksheets("存在しないシートA").Activate '① 1つ目のエラー
Exit Sub
ErrHandler:
'--- ハンドラの中で起きた2つ目のエラーは、このハンドラでは拾えない ---
Worksheets("存在しないシートB").Activate '② ここで止まる
MsgBox "この行までは届きません"
End Sub
エラーハンドラは、①でジャンプしてきた時点から「処理中」の状態に入ります。この処理中の間は、同じプロシージャで発生した新しいエラーを受け取れません。②で止まるのはそのためです。
受け取れなかったエラーは、呼び出し元のプロシージャへ渡される仕組み。上の例のようにマクロを直接実行した場合は渡す先が無いので、未処理エラーのダイアログが出て止まります。
処理中が解けるのは、Resume 系のステートメントか Exit Sub などに到達したとき。裏を返せば、ハンドラの中で Resume を使って本流に戻せば、エラー処理は再び有効な状態に戻ります。
対処は2つです。ハンドラの中には落ちにくい処理だけを置くこと。ログ書き込みのように失敗しうる処理なら、その周りだけ On Error Resume Next で守ります。
スコープ(有効範囲)もここで押さえておきましょう。On Error GoTo で指定するラベルは、同じプロシージャの中に無ければコンパイルエラーになります。
エラー処理の有効範囲はプロシージャ単位。Sub を分けた瞬間、呼び出し先には呼び出し先のエラー処理が必要になります。
【落とし穴4】Err.Clear 漏れでループの2件目以降が誤判定になる
ループの中で On Error Resume Next と Err.Number を組み合わせる。この書き方で非常によく起きるバグです。
Sub Trap_ErrClearMissing()
Dim i As Long
Dim n As Long
On Error Resume Next
For i = 2 To 10
n = CLng(Cells(i, "A").Value)
If Err.Number <> 0 Then
Cells(i, "B").Value = "ERR"
'--- Err.Clear が無いので、以降ずっと ERR 扱いになる ---
Else
Cells(i, "B").Value = n * 2
End If
Next i
On Error GoTo 0
End Sub
Err は、次に上書きされるかクリアされるまで前の情報を持ち続けます。A列の3行目で1件エラーが出たとしましょう。すると4行目以降は正常な数値でも、Err.Number <> 0 が成立してしまいます。
結果として、B列は途中から全部「ERR」で埋まります。データ側は正常なのに出力だけがおかしい、という一番気づきにくい壊れ方ですよね。
対処は、判定した直後に Err.Clear を書くこと。1件ごとに情報をリセットしてから次の反復へ進めば、誤判定は起きません。
NOTE
Errがリセットされるタイミングは、公式の資料でも説明の粒度に幅があります。ハンドラ内のExit SubやResume Nextでリセットされる、という記述があります。一方でOn Error文の実行そのものでリセットされる、という記述も存在します。ループの中で毎回Err.Numberを見る書き方なら、明示的なErr.Clearを入れておきましょう。安全側に倒す設計です。
落とさず終わらせる実務パターン5選
ここからは、そのままコピーして使えるパターンを5つ紹介します。共通のねらいは「落ちない」ではなく、落ちても後始末まで済ませて終わることです。
エラーをゼロにするのは現実的ではありません。他人が触ったブックを処理する以上、想定外はいつか起きます。だからこそ、起きた後の終わり方を設計しておきましょう。
【パターン1】ファイルが無くても止めずに知らせる
Workbooks.Open は、ファイルが無いときに実行時エラーで止まります。基本形は、事前チェックとハンドラの二重構え。
Sub Pattern1_FileNotFound()
Const FILE_PATH As String = "C:reportssales.xlsx"
Dim wb As Workbook
'--- ① まず存在チェックで弾く ---
If Dir(FILE_PATH) = "" Then
MsgBox "ファイルが見つかりません: " & FILE_PATH
Exit Sub
End If
'--- ② 開く処理はハンドラで守る ---
On Error GoTo ErrHandler
Set wb = Workbooks.Open(FILE_PATH)
MsgBox "開きました: " & wb.Name
wb.Close SaveChanges:=False
Exit Sub
ErrHandler:
MsgBox "ファイルを開けませんでした。" & vbCrLf & _
"[" & Err.Number & "] " & Err.Description
End Sub
Dir の事前チェックだけでは足りません。ファイルが存在していても、他のユーザーが開いていたり権限が無かったりすると失敗するからです。だから②のハンドラを残してあります。
メッセージにはファイルのパスを入れておくのがおすすめ。共有フォルダの移動が原因だった、というケースを一発で切り分けられますよ。
【パターン2】シートの有無で分岐する(Resume Next を1行だけに閉じる)
シートの取得は On Error Resume Next の正当な使いどころです。ただし囲むのは1行だけにします。
Sub Pattern2_SheetNotFound()
Dim ws As Worksheet
'--- Resume Next で囲むのはこの1行だけ ---
On Error Resume Next
Set ws = ThisWorkbook.Worksheets("売上データ")
On Error GoTo 0
If ws Is Nothing Then
MsgBox "シート『売上データ』が見つかりません。" & vbCrLf & _
"シート名を確認してください。"
Exit Sub
End If
ws.Range("A1").Value = "更新済み"
End Sub
Set に失敗すると ws は Nothing のまま残ります。だから結果の判定は Err.Number ではなく Is Nothing で行うのが素直。
3行のブロックで開いて閉じる。この形を守るかぎり、On Error Resume Next は危険な構文ではありません。
【パターン3】ループ内で1件だけ飛ばして最後まで回す
100件処理して1件だけ不正な値がある。そんなときに全体を止めないパターンです。
Sub Pattern3_SkipInLoop()
Dim ws As Worksheet
Dim i As Long, lastRow As Long
Dim n As Long
Set ws = ThisWorkbook.Worksheets("売上データ")
lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
For i = 2 To lastRow
On Error Resume Next
n = CLng(ws.Cells(i, "A").Value)
If Err.Number = 0 Then
ws.Cells(i, "B").Value = n * 2
Else
ws.Cells(i, "B").Value = "ERR"
Err.Clear 'ここを忘れると次の行も ERR になる
End If
On Error GoTo 0
Next i
MsgBox "処理が完了しました(" & (lastRow - 1) & "件)"
End Sub
1回の反復でやることは4つ。
On Error Resume Nextでエラー無視を開始する- 変換を試す
Err.Numberで判定し、失敗ならErr.ClearでリセットするOn Error GoTo 0で解除して次の行へ進む
飛ばした行に「ERR」と印を残しておくのがコツです。後から目視で拾えますし、件数が多ければフィルターでも抽出できますよね。
【パターン4】途中で落ちても画面更新・自動計算を必ず元に戻す
処理を速くするために ScreenUpdating や Calculation を切る書き方は定番です。ところが途中でエラーが起きると、切ったまま Excel が残ってしまいます。
画面が更新されない、数式を編集しても再計算されない、シートを変更してもイベントマクロが動かない。ユーザーからは「Excel が壊れた」ように見えます。
これらの設定は、エラーで中断したときに自動で元へ戻るとは限りません。実務では戻らない前提で書きましょう。
Sub Pattern4_SafeCleanup()
Dim prevCalc As XlCalculation
prevCalc = Application.Calculation '元の設定を控えておく
On Error GoTo Cleanup
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
'--- ここに本来の重い処理を書く ---
ThisWorkbook.Worksheets("集計").Range("A1").Value = "処理中"
Cleanup:
'--- 正常時もエラー時も必ずここを通す ---
Application.Calculation = prevCalc
Application.EnableEvents = True
Application.ScreenUpdating = True
If Err.Number <> 0 Then
MsgBox "処理を中断しました" & vbCrLf & _
"[" & Err.Number & "] " & Err.Description
End If
End Sub
このコードだけは、あえて Exit Sub を置いていません。正常に終わったときも Cleanup: を通したいからです。【落とし穴2】の意図的な例外だと理解してください。
その代わり、エラーの有無は Cleanup: の中で Err.Number を見て判定しています。この形なら、後始末は必ず1回だけ実行されます。
Calculation を xlCalculationAutomatic で固定していない点もポイント。元の値を控えて戻すことで、手動計算で運用しているブックを勝手に変えてしまう事故を防げます。
後始末を入れる前のマクロが落ちて画面が固まったままなら、応急処置もありますよ。イミディエイトウィンドウに Application.ScreenUpdating = True と打てば戻せます。
使い方はVBAイミディエイトウィンドウの使い方へ。マクロ自体が止まらないときはExcelマクロが止まらない時の強制終了4ステップを参照してください。
【パターン5】エラー内容をログに残して後から追う
他人に配布するマクロでは、エラーの記録が最大の武器になります。「エラーが出ました」という報告だけでは、こちらは何もできませんからね。
Sub Pattern5_ErrorLog()
On Error GoTo ErrHandler
'--- 通常処理 ---
Workbooks.Open ThisWorkbook.Path & "sales.xlsx"
Exit Sub
ErrHandler:
'--- ① Err の中身を先に変数へ控える ---
Dim errNum As Long, errDesc As String
errNum = Err.Number
errDesc = Err.Description
'--- ② ログ書き込みは Resume Next で守る ---
Dim logPath As String, ff As Integer
logPath = ThisWorkbook.Path & "error.log"
On Error Resume Next
ff = FreeFile
Open logPath For Append As #ff
Print #ff, Format(Now, "yyyy-mm-dd hh:nn:ss") & vbTab & _
"Pattern5_ErrorLog" & vbTab & _
errNum & vbTab & errDesc
Close #ff
On Error GoTo 0
MsgBox "処理に失敗しました。" & vbCrLf & _
"error.log を管理者に送ってください。"
End Sub
日時・プロシージャ名・エラー番号・説明をタブ区切りで追記しています。Excel で開けばそのまま列に分かれるので、発生頻度の集計も簡単。Open した以上は Close を忘れないでくださいね。
ログ書き込みが失敗しても止まらないよう、On Error Resume Next で囲んでいます。【落とし穴3】で触れた、ハンドラの中を守る書き方です。
エラー番号と説明を先に変数へ控えているのもポイント。On Error を実行すると、Err の中身がリセットされる場合があるからです。
自分だけで使うマクロなら、ファイル出力までは不要です。Debug.Print でイミディエイトウィンドウに出す軽い方法でも十分ですよ。
よくある質問(FAQ)
On Error GoTo を書いたのにエラーで止まります
原因は大きく3つに分かれます。
- VBE の「エラー トラップ」設定が影響している
- ハンドラの中で2つ目のエラーが起きている
- 別のプロシージャの中でエラーが起きている
まず環境を疑ってください。他の人の PC では止まらないなら、【落とし穴1】の設定を確認します。自分の PC だけの問題ではないなら、【落とし穴3】のハンドラ内エラーを疑いましょう。
On Error Resume Next は絶対に使ってはいけないの?
そんなことはありません。シートやファイルの存在確認のように、「失敗しても次へ進みたい」処理では正当な選択肢です。
危ないのは構文そのものではなく、囲む範囲が広いこと。1〜2行だけに閉じて、直後に On Error GoTo 0 で解除する。この形を守れば、パターン2やパターン3のように安全に使えます。
On Error GoTo 0 を書き忘れるとどうなりますか?
そのプロシージャを抜けるまで、エラー処理の設定が効き続けます。On Error Resume Next のまま進むと、以降のエラーがすべて無言で握りつぶされる状態に。
ただし、プロシージャを抜ければ設定は自動的に無効になります。他の Sub にまで漏れ出すわけではありません。とはいえ影響範囲を目で追えなくなるので、書く習慣にしておきましょう。
別の Sub を呼び出したとき、エラー処理は引き継がれますか?
引き継がれない前提で書いてください。On Error Resume Next は、別のプロシージャが呼び出された時点で効果が続きません。呼び出し先でもエラーを無視したいなら、その中で改めて書く必要があります。
On Error GoTo のラベルも同じプロシージャ内に限定されます。ハンドラが処理中の状態で新しいエラーが起きた場合は、制御が呼び出し元へ戻ります。
呼び出し元に有効なハンドラがあれば、そちらが起動する仕組み。ただし細かい挙動は条件で変わるため、呼び出し先にも受け皿を書くのが確実です。
VBAに Try-Catch はありますか?
VBA に Try-Catch 構文はありません。代わりに On Error GoTo ラベル と Exit Sub、そしてラベル位置の処理を使います。この3つの組み合わせで、同じことを実現できます。
本記事の最小テンプレートが、そのまま Try-Catch に相当する形です。Cleanup: を使うパターン4は Finally に近い役割。そう考えると、対応関係が分かりやすいのではないでしょうか。
エラー処理を入れるとマクロは遅くなりますか?
エラーが起きないかぎり、体感できるほどの差はまず出ません。気にするとしたら、ループの中で On Error を毎回呼び直す構成くらいですね。
数万行規模のデータで速度が気になるなら、事前チェック方式に切り替えてみてください。IsNumeric などで先に弾く形です。決定表の3行目「そもそも起こさせない」に寄せる発想ですね。
まとめ
VBA のエラー処理でつまずくポイントは、文法そのものではありません。On Error を書いた後に何が起きるかを知らないまま、配布してしまうことです。
- 書き分けは3択: 止める(
On Error GoTo)/続ける(On Error Resume Nextを1〜2行に閉じる)/起こさせない(事前チェック) - 効かない原因の筆頭: VBE の「エラー トラップ」設定、
Exit Sub忘れ、On Error GoTo 0の解除漏れ、Err.Clear漏れ - ハンドラは処理中に二度目を拾えない: ハンドラの中には落ちにくい処理だけを置く
- 後始末まで書いて完成:
ScreenUpdatingやCalculationは戻らない前提でCleanup:に集約する
まずはパターン4の後始末テンプレートを、いま手元にあるマクロへ1本入れてみてください。落ちたときに Excel が変な状態で残らなくなるだけでも、配布先からの問い合わせはぐっと減るはずです。
エラー番号ごとの原因と直し方はVBAマクロのエラー解決ガイドへ。VBA を体系的に身につけたい方は、入門ハブのExcelのVBAで仕事を自動化する方法もあわせて読んでみてくださいね。
