【VBA極限知見】On Error GoToの罠:エラー発生時の変数状態の整合性を完全制御する設計論
開発現場でよく見かける、次のようなコード。あなたは何の疑問も持たずに書いていないだろうか?
Sub BadExample()
Dim ws As Worksheet
Dim dbConn As Object
On Error GoTo ErrorHandler
Set ws = Worksheets(“Data”)
Set dbConn = CreateObject(“ADODB.Connection”)
dbConn.Open “Provider=…”
‘ 何らかの重い処理…
ws.Range(“A1”).Value = dbConn.Execute(“SELECT …”).Fields(0).Value
Exit Sub
ErrorHandler:
MsgBox “エラーが発生しました: ” & Err.Description
‘ ここで処理を抜けるが、変数の後始末はどうなっている?
End Sub
一見すると、`On Error GoTo`を使った一般的なエラーハンドリングに見える。だが、プロのアーキテクトの視点から言わせれば、このコードは「バグの温床」であり、実務の現場において最も恐ろしいサイレントキラーだ。
なぜか? エラーが発生して `ErrorHandler` にジャンプした瞬間、メモリ上に確保されたオブジェクト(DB接続、ファイルハンドラ、外部COMコンポーネント)や、中途半端に書き換わった変数の状態が野ざらしのまま放置されるからだ。
今回は、Excel VBAにおけるエラーハンドリングと変数の初期化・整合性管理の極意を、プロダクションレベルのコードと共に伝授する。
—
1. なぜ「その場のエラー処理」では破綻するのか?
VBAの実行時エラーが発生すると、プログラムの制御は `On Error GoTo [ラベル]` にジャンプする。この時、以下の深刻な問題が発生する。
1. オブジェクトのリークとロック
データベースのコネクションやファイルシステムオブジェクトが解放されないまま残り、次にマクロを実行した際に「リソースが不足しています」「ファイルが使用中です」といった不可解なエラーを引き起こす。
2. 「汚染された変数」の再利用リスク
同一モジュール内で変数を使い回している場合(特にstatic変数やモジュールレベル変数)、エラー発生時の不完全な値がそのまま次の処理に持ち越される。
3. トランザクションの不整合
画面描画の抑制(`ScreenUpdating = False`)や計算の手動化(`Calculation = xlCalculationManual`)を解除する前に処理が中断すると、Excelが使い物にならない(フリーズしたような)状態でユーザーに放置される。
プロのエンジニアにとって、エラーハンドリングとは単に「エラーメッセージを出すこと」ではない。「異常系から正常系(または安全な停止状態)へ、システムを完璧に巻き戻すこと(ロールバック)」である。
—
2. 堅牢なエラーハンドリングの設計原則
実務で通用する堅牢なツールを作るためには、以下の3つの鉄則を守る必要がある。
- 原則1:リソースの二重解放を防ぐクリーンアップブロックの統一
- 原則2:環境設定(ScreenUpdating等)は必ずFinally句(終了処理ブロック)で復元する
- 原則3:変数はスコープを最小限にし、エラー脱出時には確実に初期化・解放する
これをVBAで実現するための最も美しいデザインパターンが、「Single Exit & Cleanup Pattern(単一出口とクリーンアップパターン)」だ。
—
3. 【実践】プロダクションコード:完全整合性管理テンプレート
ファイル操作とデータベース(または外部API連携)を模擬した、実務でそのまま使えるテンプレートコードを提示する。変数の初期化、エラー時の捕捉、そして確実な後始末の構造をその目で確認してほしい。
Option Explicit
‘ =========================================================================
‘ 業務自動化ツール:月次データインポート処理
‘ アーキテクチャ設計:Single Exit & Cleanup Pattern
‘ =========================================================================
Sub ProductionDataImport()
‘ 1. 変数の宣言(スコープは最小限に、かつ最初に宣言)
Dim wsTarget As Worksheet
Dim lngLastRow As Long
Dim fso As Object
Dim ts As Object
Dim blnScreenState As Boolean
Dim blnCalcState As Boolean
‘ 2. エラーハンドラーの有効化
On Error GoTo ErrorHandler
‘ ———————————————————————
‘ 3. 環境設定の退避と最適化(パフォーマンス向上)
‘ ———————————————————————
blnScreenState = Application.ScreenUpdating
blnCalcState = Application.Calculation
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
‘ ———————————————————————
‘ 4. メイン処理(リソースの取得と操作)
‘ ———————————————————————
Set wsTarget = ThisWorkbook.Sheets(“ImportData”)
‘ 既存データのクリア(変数の整合性を保つため、前回の残滓を排除)
lngLastRow = wsTarget.Cells(wsTarget.Rows.Count, “A”).End(xlUp).Row
If lngLastRow > 1 Then
wsTarget.Range(“A2:Z” & lngLastRow).ClearContents
End If
‘ FileSystemObjectの初期化とファイル読み込み
Set fso = CreateObject(“Scripting.FileSystemObject”)
Dim filePath As String
filePath = ThisWorkbook.Path & “\import_source.csv”
If Not fso.FileExists(filePath) Then
Err.Raise 513, “DataImport”, “インポート対象のCSVファイルが存在しません: ” & filePath
End If
Set ts = fso.OpenTextFile(filePath, 1) ‘ ForReading
‘ (ここに実際のデータ読み込み・展開処理が入る)
‘ 例: wsTarget.Range(“A2”).Value = ts.ReadAll
‘ ———————————————————————
‘ 5. 正常終了ルート(クリーンアップへジャンプ)
‘ ———————————————————————
GoTo CleanUp
ErrorHandler:
‘ ———————————————————————
‘ 6. 異常系ハンドリング
‘ ———————————————————————
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“内容: ” & Err.Description, vbCritical, “システムエラー”
‘ 必要に応じてロールバック処理(データの復元など)をここに記述
CleanUp:
‘ ———————————————————————
‘ 7. 究極のクリーンアップ処理(正常・異常にかかわらず必ず実行)
‘ ———————————————————————
‘ オブジェクトの確実な解放(メモリリークの防止)
On Error Resume Next ‘ 解放時のエラーを無視して続行
If Not ts Is Nothing Then ts.Close
Set ts = Nothing
Set fso = Nothing
Set wsTarget = Nothing
On Error GoTo 0 // エラー監視を復元
‘ 環境設定の復元(ユーザーのExcel環境を絶対に汚さない)
Application.ScreenUpdating = blnScreenState
Application.Calculation = blnCalcState
Application.EnableEvents = True
Exit Sub
End Sub
—
4. コードの解説:なぜこの設計が最強なのか?
① `GoTo CleanUp` による単一の出口
プログラミングのアンチパターンとして「複数の `Exit Sub` とバラバラに書かれたエラー処理」がある。これでは仕様変更の際に必ずリソース解放の漏れが発生する。
このコードでは、正常終了であっても一度 `CleanUp:` ラベルにジャンプさせ、「すべての変数の解放と環境の復元」を1箇所に集約している。
② 解放時の `On Error Resume Next`
オブジェクトを `Set obj = Nothing` する際、すでにそのオブジェクトがクラッシュしているか、初期化途中でエラーが起きて `Nothing` の状態である場合、解放処理自体で新たなエラーが発生することがある。
これを防ぐため、クリーンアップブロックの直前で一時的に `On Error Resume Next` を挟み、「後始末の途中でエラーによって処理が中断する事態」を完全に封じ込めている。
③ 環境設定の完全復元
業務ツールで最も嫌われるのが、「マクロを実行したら、その後Excelの画面が真っ白になったまま動かなくなった」「自動計算が手動のままになって他のシートの数式が計算されなくなった」というトラブルだ。
プログラマーが意図した処理であれ、予期せぬエラーであれ、処理開始前に取得した環境変数の状態(`blnScreenState` 等)を `CleanUp` で必ず元の状態に戻す。これがプロの仕事である。
—
5. チーフアーキテクトからのメッセージ
「動けばいい」というレベルのコードと、実務の現場で何千回も耐えうる「プロダクション品質」のコードの差は、まさにこのエラー時および終了時の変数・リソース管理にある。
VBAはガベージコレクションの挙動が他言語(C#やJavaなど)に比べてブラックボックスであり、開発者自身がメモリやCOMオブジェクトのライフサイクルを厳密に意識して管理しなければならない。
あなたが次に業務自動化ツールを設計するとき、ぜひこの「Single Exit & Cleanup Pattern」を導入してほしい。バグの問い合わせに怯えることのない、圧倒的に堅牢で美しいシステム構築をお約束しよう。
