Access VBAのエラー処理入門|On Error文とErrオブジェクトの実務
Access VBAのエラー処理はOn Error GoTo/Resume Next/GoTo 0の3構文が基本。Errオブジェクト(Number・Description)の使い方、エラーハンドラの正しい構造、Resume Nextの乱用がデータ不整合を隠す理由、ログ記録の実務パターンを解説します。
結論:On Error GoToでエラーハンドラを書くのが実務の基本
Access VBAのエラー処理には3つの構文があります。On Error GoTo ラベル、On Error Resume Next、On Error GoTo 0です。このうち実務で中心になるのはOn Error GoToです。エラーが起きたときに処理を明示的な場所へ飛ばし、原因を記録してから復帰や終了を選べます。
On Error Resume Nextはエラーをスキップして次の行へ進む構文ですが、これを広い範囲に使うと不具合が見えなくなります。「エラーが起きても動いているように見える」状態になるため、問題の発見が遅れます。使い方を誤ると、後からデータに矛盾が生じても原因が追えなくなります。
Microsoftの公式ドキュメント(VBA言語リファレンス: On Error文)には「On Error文を使わない場合、実行時エラーはすべて致命的になる(エラーメッセージが表示されて実行が停止する)」と明記されています。エラー処理を書かないのは最悪ですが、書き方を間違えると問題を隠すだけになります。
3つの構文の動作を整理する
まず構文ごとの動作を表でまとめます。
| 構文 | エラー発生時の動作 | 主な用途 |
|---|---|---|
On Error GoTo ラベル | 指定したラベル行へジャンプし、エラーハンドラが実行される | 実務の基本。エラーの記録・メッセージ表示・後処理に使う |
On Error Resume Next | エラーが起きた行の次の行から実行を続ける | オブジェクト操作の直後にErrを確認する用途に限定して使う |
On Error GoTo 0 | 現在のプロシージャでエラーハンドラを無効化する | エラーハンドラを一時的に解除したい場面 |
公式ドキュメントによると、On Error GoTo 0は「行番号0へジャンプする」という意味ではなく、エラー処理そのものを無効化します。また、On Error Resume Nextはプロシージャを呼び出すたびに有効範囲が切れるため、呼び出し先でも使いたい場合は呼び出し先にも個別に記述する必要があります。
Errオブジェクトのプロパティと使い方
エラーの原因を調べるにはErrオブジェクトを参照します。主なプロパティはNumberとDescriptionです。
| プロパティ/メソッド | 内容 |
|---|---|
Err.Number | エラー番号(整数)。エラーがない場合は0 |
Err.Description | エラーの説明テキスト |
Err.Source | エラーを発生させたオブジェクトやアプリケーションの名前 |
Err.Clear | Errオブジェクトのすべてのプロパティをリセットする |
Err.Raise | 意図的にエラーを発生させる(テストや再スローに使う) |
公式ドキュメント(VBA言語リファレンス: Errオブジェクト)には「Errオブジェクトのプロパティは、エラーハンドラ内でExit Sub・Exit Function・Exit Property・Resume Nextを実行するとリセットされる」と書かれています。エラーハンドラを抜けた後でもErrを参照したい場合は、ハンドラ内で別の変数に退避しておく必要があります。
基本的なエラーハンドラの書き方
実務でよく使う構造を示します。
Sub ImportData()
On Error GoTo ErrHandler
' 処理本体
Dim db As DAO.Database
Set db = CurrentDb()
' ... データ操作など ...
Exit Sub ' ← ここが重要。ハンドラへの落下を防ぐ
ErrHandler:
MsgBox "エラーが発生しました。" & vbCrLf & _
"エラー番号: " & Err.Number & vbCrLf & _
"内容: " & Err.Description, vbCritical, "エラー"
' 必要に応じてログ記録やロールバックをここに書く
End SubExit SubをErrHandler:ラベルの直前に置く点がポイントです。これがないと、エラーがない正常ケースでもハンドラコードが実行されてしまいます。公式ドキュメントにも「Exit Sub・Exit Function・Exit Propertyをエラー処理ルーチンの直前に置くこと」と明記されています。
Resumeステートメントには3種類あります。
| 構文 | 動作 |
|---|---|
Resume | エラーが起きた行に戻って再実行する |
Resume Next | エラーが起きた行の次の行から実行を再開する |
Resume ラベル | 指定したラベル行から実行を再開する |
Resumeはエラーハンドラの内部でしか使えません。エラーハンドラの外で使うとそれ自体がエラーになります(公式ドキュメントによる)。
On Error Resume Nextの乱用が不具合を隠す理由
On Error Resume Nextを誤って広範囲に使うと何が起きるか、具体的に見てみます。
' ×悪い例:Resume Nextをプロシージャ全体に使う
Sub DeleteRecord()
On Error Resume Next
Dim db As DAO.Database
Set db = CurrentDb()
db.Execute "DELETE FROM 受注テーブル WHERE ID = 999", dbFailOnError
' dbFailOnError指定でも、Resume Nextによりエラーが起きても次の行へ進む
' 削除に失敗しても Err を確認せず終わるため、問題に気づかない
End Subこの書き方の問題は、db.Executeがエラーを返してもErrを確認せずに処理を続けてしまう点です。エラーが黙って飲み込まれ、後から原因を追えなくなります。なお、対象レコードが0件のUPDATE文はVBAのエラーにならない(エラーが起きていない)ため、On Error GoToでも捕捉できません。更新件数を確認したい場合はdb.Execute "UPDATE ...", dbFailOnErrorの後でdb.RecordsAffectedを参照します(dbFailOnErrorは外さずそのまま使えます)。
一方、On Error Resume Nextが適切な場面もあります。オブジェクトにアクセスした直後にErrを確認する用途です。公式ドキュメントにも「オブジェクトへのアクセス時はOn Error Resume Nextが望ましい場合がある」と書かれています。
' ○適切な例:Resume Next後にErrを確認する
Sub CheckObject()
On Error Resume Next
Dim obj As Object
Set obj = GetObject("存在しないオブジェクト")
If Err.Number <> 0 Then
MsgBox "オブジェクトを取得できませんでした: " & Err.Description
Err.Clear
End If
On Error GoTo 0 ' 確認が終わったら無効化する
End Subポイントは、On Error Resume Nextを使う範囲を最小限に絞り、確認が終わったらOn Error GoTo 0で無効化することです。
実務で役立つパターン:ログ記録と属人化の問題
業務用のAccessで問題になりやすいのは、エラーが起きても記録が残らない作りです。誰かがMsgBoxを閉じれば処理は終わり、何が起きたか後から追えません。ログをテーブルに記録する方法が実用的です。
Sub ImportDataWithLog()
On Error GoTo ErrHandler
' 処理本体
Dim db As DAO.Database
Set db = CurrentDb()
' ... 処理 ...
Exit Sub
ErrHandler:
Dim errNum As Long
Dim errDesc As String
errNum = Err.Number
errDesc = Err.Description
' Errオブジェクトは別の処理で上書きされる前に退避する
' ログ書き込みは別プロシージャへ委譲(ハンドラ内の二重エラーを避ける)
WriteErrorLog errNum, errDesc
MsgBox "処理中にエラーが発生しました。管理者に連絡してください。" & vbCrLf & _
"(エラー番号: " & errNum & ")", vbCritical, "エラー"
End Sub
' ログ書き込みを独立したプロシージャに分離する
' (エラーハンドラが実行中に二重エラーが発生すると呼び出し元へ伝播するため)
Sub WriteErrorLog(errNum As Long, errDesc As String)
On Error Resume Next ' ログ失敗は握りつぶす(本処理への影響を防ぐ)
Dim db As DAO.Database
Set db = CurrentDb()
' アポストロフィを含む説明文でもSQL構文エラーにならないようエスケープする
Dim safeDesc As String
safeDesc = Replace(errDesc, "'", "''")
db.Execute "INSERT INTO エラーログ (発生日時, エラー番号, 内容) " & _
"VALUES (Now(), " & errNum & ", '" & safeDesc & "')"
End SubErr.NumberとErr.Descriptionを変数へ退避してから記録処理へ渡すのは、ログ書き込み中に別のエラーが発生してErrオブジェクトが上書きされるのを防ぐためです。また、ログ書き込みを別プロシージャ(WriteErrorLog)に分離しているのも同じ理由です。エラーハンドラが実行中のプロシージャ内で二重エラーが起きると、公式ドキュメントの記述どおり現在のハンドラでは捕捉できず呼び出し元へ制御が戻ってしまいます。ログ専用プロシージャの中でOn Error Resume Nextを使えば、ログ失敗を静かに握りつぶしつつ、メイン処理のエラーハンドラの動作を妨げません。
ユーザーへのメッセージと内部ログは分けるのが原則です。「エラー番号53: ファイルが見つかりません」を業務ユーザーに見せても意味がありません。ユーザーには「処理に失敗しました。管理者に連絡してください」と伝え、番号は参考程度に添えるか、ログにだけ残します。
こうしたエラー処理の設計が属人的になると、引き継ぎの際に「どこで何のエラーが起きうるか」が誰にもわからなくなります。担当者が退職した後のAccess DBで起きやすい問題については、Access属人化の危険度と脱・属人化の進め方でまとめています。属人的なVBAが積み重なった状態では、移行の際に「何をしているコードか」を解読するところから始める必要があります。
現状のAccessにどれだけ複雑なVBAが絡んでいるか確認したい場合は、無料の解析可否チェックから状況を整理することもできます。
VBAとマクロの使い分けについてはAccessのマクロとVBAの違いと使い分け入門も参考にしてください。
よくある質問
Q. On Error Resume Nextは使ってはいけないですか。
使い方次第です。オブジェクトへのアクセスの直後にErrを確認してからOn Error GoTo 0で無効化する、という絞った使い方なら有効です。問題なのは、プロシージャ全体をResume Nextで包んでErrを確認せずに終わらせる使い方です。エラーが黙って飲み込まれ、データの不整合や処理の失敗に気づかなくなります。
Q. Err.Number と Err.Description はいつリセットされますか。
公式ドキュメントによると、エラーハンドラ内でExit Sub・Exit Function・Exit Property・Resume Nextを実行したタイミングでリセットされます。またErr.Clearメソッドを呼べば明示的にリセットできます。On Error文を実行したタイミングでもErrはクリアされます。エラーハンドラ内でErrを参照する前に別の処理を行うと上書きされる可能性があるため、ハンドラの先頭でNumberとDescriptionを変数へ退避するのが安全です。
Q. Resume と Resume Next の使い分けは?
Resumeはエラーが起きた行を再実行します。たとえばファイルが開いていてエラーになった場合、ファイルを閉じてからResumeすれば同じ行を再試行できます。Resume Nextはエラーが起きた行をスキップして次の行に進みます。どちらもエラーハンドラの外で使うとそれ自体がエラーになる点に注意してください。
Q. エラーハンドラの中でエラーが発生したらどうなりますか。
公式ドキュメントによると、エラーハンドラが実行中(On Error GoToで処理が始まってからResume・Exit Sub等が完了するまで)に別のエラーが発生した場合、その手続きのエラーハンドラでは捕捉できず、呼び出し元のエラーハンドラへ制御が戻ります。呼び出し元にも有効なハンドラがなければ、エラーは致命的になります。このため、ログ書き込みのような副処理はエラーハンドラ内から直接実行せず、独立したプロシージャへ委譲し、その中で個別にOn Error Resume Nextを使う設計が安全です。
触れないAccessが「診断できるか」だけ、確かめませんか。
顧客データは送信不要。発注の義務もありません。
約2分・ファイル送信不要・発注義務なし/説明はオンライン・売り込みはしません