聖域への回帰:Word VBAにおける「不確実性」を制御するエラーロギングの極致
Word VBAという領域は、Excel VBAに比べて遥かに「流動的」であり、そして「脆弱」である。Excelがセルの座標という静的なグリッドに支配されているのに対し、Wordは「Range(範囲)」という、ドキュメントの編集状態によって常に変動する不確実な座標系の上に成り立っているからだ。
熟練のシニアエンジニアならば、Wordオブジェクトモデルの操作がいかに容易に「実行時エラー」を引き起こすかを知っているはずだ。COMサーバーの予期せぬハング、セクション区切りの不整合、あるいはユーザーによる意図しない文書保護。これらが牙を剥いたとき、ただの `MsgBox` で逃げるのはアマチュアの所業である。
真のプロフェッショナルは、エラーが発生した瞬間の「現場の証拠」を、ミリ秒単位の精度でログに刻印する。本稿では、保守性を極限まで高めるためのエラーハンドリング・アーキテクチャについて、Windows APIを活用した高精度なロギング実装とともに解説する。
—
1. 「On Error GoTo」の再定義:構造化例外処理への昇華
VBAにはモダンな言語のような `try-catch-finally` は存在しない。しかし、ラベルを用いた構造化設計により、それに準ずる堅牢なフレームワークを構築することは可能だ。
Word VBAにおいて特に注意すべきは、オブジェクトのライフサイクル管理である。エラーが発生した際、`Range` や `Document` オブジェクトがメモリ上に残置されると、Winword.exeのプロセスがゾンビ化し、次回の実行を阻害する。エラーハンドリングの主目的は「報告」だけでなく、「クリーンアップ(資源解放)」にある。
—
2. 高精度ロギング・エンジンの設計
標準の `Print #` ステートメントによるログ出力は簡便だが、システム間連携やマルチプロセス環境を想定した場合、書き込み競合やバッファリングの制御に不安が残る。ここでは、システムの低層レイヤーに触れることで、より決定論的な挙動を保証する設計を導入する。
実装:Loggerモジュール (mod_Logger)
このモジュールは、Windows API `GetLocalTime` を使用してミリ秒単位のタイムスタンプを取得し、ファイルシステムへの書き込みをカプセル化する。
Option Explicit
‘ Windows API: システム時間をミリ秒単位で取得
Private Type SYSTEMTIME
wYear As Integer
wMonth As Integer
wDayOfWeek As Integer
wDay As Integer
wHour As Integer
wMinute As Integer
wSecond As Integer
wMilliseconds As Integer
End Type
Private Declare PtrSafe Sub GetLocalTime Lib “kernel32″ (lpSystemTime As SYSTEMTIME)
”’
”’
Public Sub WriteErrorLog(ByVal procName As String, ByVal errObj As ErrObject)
Dim st As SYSTEMTIME
Dim logMsg As String
Dim logPath As String
Dim fNo As Integer
GetLocalTime st
‘ ログファイルのパス(実行中のドキュメントと同一ディレクトリを想定)
On Error Resume Next
logPath = ThisDocument.Path & “\error_log_” & Format(Date, “yyyymmdd”) & “.log”
On Error GoTo 0
‘ ログフォーマットの構築(ISO 8601準拠 + ミリ秒)
logMsg = sprintf(“[%04d-%02d-%02d %02d:%02d:%02d.%03d] “, _
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds)
logMsg = logMsg & “[PROC]: ” & procName & ” ”
logMsg = logMsg & “[ERR]: ” & errObj.Number & ” – ” & errObj.Description
‘ ファイル出力(排他制御を考慮した追加書き込み)
fNo = FreeFile
Open logPath For Append Access Write As #fNo
Print #fNo, logMsg
Close #fNo
‘ デバッグウィンドウにも出力
Debug.Print logMsg
End Sub
‘ 簡易的な書式設定関数
Private Function sprintf(ByVal mask As String, ParamArray args()) As String
Dim i As Integer
For i = LBound(args) To UBound(args)
mask = Replace(mask, “%” & Format(i + 1, “00”) & “d”, Format(args(i), “0”), 1, 1)
Next
sprintf = mask
End Function
—
3. オブジェクトモデルの掌握:実践的なエラーハンドリング
Word VBAのメインロジックにおいて、どのようにこのLoggerを呼び出し、かつリソースを解放すべきか。ポイントは、「正常終了パス」と「エラー終了パス」を必ず共通の「終了処理(Finally)」へ合流させることだ。
実装:ビジネスロジック (mod_Main)
Option Explicit
”’
”’
Public Sub ExecuteDocumentProcessing()
Const PROC_NAME As String = “ExecuteDocumentProcessing”
Dim doc As Word.Document
Dim rng As Word.Range
Dim isSuccess As Boolean: isSuccess = False
‘ 1. オブジェクトの初期化
On Error GoTo ErrorHandler
Set doc = ThisDocument
‘ Word特有の不安定要素(Range操作)の開始
‘ ここでは意図的にエラーが起きやすい「複雑な検索・置換」を想定
Set rng = doc.Content
With rng.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = “置換前キーワード”
.Replacement.Text = “置換後データ”
.Forward = True
.Wrap = wdFindContinue
.Format = False
‘ ここでCOMエラーや読み取り専用エラーが発生する可能性がある
If Not .Execute(Replace:=wdReplaceAll) Then
‘ 業務的な失敗もエラーとして扱う設計思想
Err.Raise 1001, PROC_NAME, “キーワードの置換に失敗しました。対象が見つからないか、文書が保護されています。”
End If
End With
isSuccess = True
MsgBox “処理が正常に完了しました。”, vbInformation
Finalize:
‘ 2. メモリ最適化:オブジェクトの明示的解放
‘ WordのCOM参照カウンタを確実に減らすための儀式
Set rng = Nothing
Set doc = Nothing
‘ 3. パフォーマンス維持:画面更新の再開(止めていた場合)
Application.ScreenUpdating = True
Exit Sub
ErrorHandler:
‘ 4. エラーロギング:現場の状況を永続化
WriteErrorLog PROC_NAME, Err
‘ ユーザーへの通知(技術詳細はログにあるため、簡潔に)
MsgBox “致命的なエラーが発生しました。ログを確認してください。” & vbCrLf & _
“Error: ” & Err.Number, vbCritical, “システムエラー”
Resume Finalize
End Sub
—
4. 伝説的チーフアーキテクトの視点:なぜここまでやるのか
COM参照カウンタの地獄を避ける
Word VBAにおいて、`Set obj = Nothing` を怠るエンジニアは多い。しかし、Wordはアウトプロセスで動作するCOMサーバーである。特に他のアプリケーション(ExcelやAccess)からWordを制御する場合、参照が一つでも残っていると `Winword.exe` はタスクマネージャーに残り続け、ファイルロックやメモリリークの原因となる。`Finalize` ラベルによる強制的な解放は、24時間稼働する自動化サーバーにおいて必須の作法である。
決定論的なデバッグ
「たまに動かない」――これは開発現場における最悪の報告だ。
今回実装したロギング機構は、エラー番号だけでなく、発生したプロシージャ名を特定する。さらに、必要に応じて `rng.Start` や `rng.End` といった、エラー時のカーソル位置情報をログに含めるよう拡張すべきだ。Wordのドキュメント構造は動的であり、エラー時の「場所」がわからなければ、再現は不可能に近い。
レガシー環境との共存
`FileSystemObject` (FSO) を使わず、あえて `Open #` や Windows API に触れるのは、参照設定のトラブル(DLLのバージョン不整合など)を最小限に抑えるためだ。大規模な組織における社内システムでは、実行環境のライブラリ構成を制御できないことが多い。依存性を減らし、標準機能のみで堅牢なロギングを実現することこそが、真の「移植性」を生む。
—
結論
Word VBAは、枯れた技術ではない。それは、非定型な文書データという「カオス」を制御するための、現役のフロントラインである。
堅牢なエラーハンドリングと詳細なロギングは、コードの行数を増やす無駄な作業ではない。それは、未来の自分、そしてシステムの保守を引き継ぐ後任者に対する、エンジニアとしての敬意の証である。本稿に示したアーキテクチャを君のプロジェクトに組み込み、不確実なWordの世界を完全に支配してほしい。
