【実務・中級編】【実務直結】Visio VBAでのエラーハンドリング:ページ操作失敗時に「どのページで何が起きたか」を詳細に記録する – Visio VBA解析バイブル

スポンサーリンク
スポンサーリンク

「動けばいい」という甘えを捨てろ:Visio VBAにおける、コンテキストを維持する極限のエラーハンドリング設計

業務自動化の現場において、プロフェッショナルとアマチュアを分かつ境界線はどこにあるか?

それは「正常系を実装する能力」ではない。「異常系が発生した際、いかに速やかに、正確に、原因を特定できる状態を作り出せるか」という、観測可能性(Observability)への執念である。

Visioの自動化、特にページ操作やレイヤー操作を伴う複雑なロジックにおいて、単純な `On Error GoTo ErrorHandler` だけを記述するのは、設計放棄に等しい。エラーが発生した瞬間に、プログラムの実行コンテキスト(今どのページを操作していたのか、どの図形に触れていたのか)は霧の中に消える。数千ページを処理するバッチ処理が、99%完了したタイミングで「実行時エラー ‘1004’」を吐いて停止したとき、君はどこから調査を始める? ページを一つずつ手作業で確認するか?

そんな非効率な作業は、アーキテクトの仕事ではない。

本稿では、実務レベルで通用する、「エラー発生時のコンテキスト(ページ情報・操作対象)を自動的にスタックトレースとして記録する」堅牢なエラーハンドリング・フレームワークを伝授する。

1. なぜ、標準的なエラーハンドリングでは不十分なのか

多くの開発者が陥る罠は、エラーメッセージ(`Err.Description`)だけを頼りにすることだ。しかし、Visio VBAにおけるエラーは、オブジェクトのライフサイクルと密接に結びついている。

  • ページ削除の失敗: 参照されているシェイプが残っているのか、それともページが保護されているのか?
  • サイズ変更の失敗: ページ設定の制約(PageSetup)に抵触したのか、それともメモリ不足か?

これらの情報は、エラーオブジェクトそのものには含まれていない。「エラーが起きた瞬間の、Visioドキュメントの状態」を、自前でキャプチャしてログに叩き込む設計が必要なのだ。

2. アーキテクチャ設計:コンテキスト・アウェア・ロギング

堅牢な設計の肝は、エラーハンドリングを「単なる例外処理」ではなく、「状態の記録(State Logging)」と定義することにある。

以下の3要素をログに含める設計指針を持つこと。
1. Temporal Context: 発生時刻、実行ユーザー。
2. Structural Context: 現在の `ActivePage.Name`、`ActivePage.Index`、操作対象の `Shape.ID`。
3. Error Context: `Err.Number`、`Err.Description`、およびエラー発生時のコード行。

3. プロダクション・グレードのコード実装

以下に、そのまま実務の基盤として転用可能な、クラスモジュールを活用したロギング・テンプレートを示す。ここでは、保守性を高めるためにロギング機能を独立させている。

Step 1: Loggerクラスの作成 (Class Module: `clsLogger`)

ログの出力先(ファイル)の管理と、フォーマット化を一手に引き受ける。

' Class Module: clsLogger
Option Explicit

Private targetFile As String

' ログファイルの初期化
Public Sub Initialize(filePath As String)
    targetFile = filePath
End Sub

' 詳細なエラーログの書き出し
' context: エラー発生時の状況(ページ名、シェイプ名など)を文字列で渡す
Public Sub LogError(errNum As Long, errDesc As String, context As String)
    Dim fileNum As Integer
    fileNum = FreeFile
    
    On Error Resume Next ' ロガー自体のエラーでメイン処理を止めないための防衛策
    
    Open targetFile For Append As #fileNum
    Print #fileNum, "--------------------------------------------------"
    Print #fileNum, "[ERROR TIME] " & Now
    Print #fileNum, "[ERROR CODE] " & errNum
    Print #fileNum, "[DESCRIPTION] " & errDesc
    Print #fileNum, "[CONTEXT]     " & context
    Print #fileNum, "--------------------------------------------------"
    Close #fileNum
    
    On Error GoTo 0
End Sub

' 進行状況の記録(デバッグ用)
Public Sub LogInfo(msg As String)
    Dim fileNum As Integer
    fileNum = FreeFile
    On Error Resume Next
    Open targetFile For Append As #fileNum
    Print #fileNum, "[INFO] " & Now & " : " & msg
    Close #fileNum
    On Error GoTo 0
End Sub

Step 2: メインロジックの実装 (Standard Module)

コンテキストを意識したエラーハンドリングの適用例。

' Standard Module: MainModule
Option Explicit

Sub ExecuteComplexPageOperations()
    Dim logger As New clsLogger
    Dim doc As Visio.Document
    Dim pg As Visio.Page
    Dim i As Integer
    Dim currentContext As String ' エラー発生時の状況を保持する変数
    
    ' 1. 環境設定
    Set doc = ThisDocument
    logger.Initialize ThisDocument.Path & "\Automation_Log_" & Format(Date, "yyyymmdd") & ".log"
    
    logger.LogInfo "Batch Process Started."

    ' 2. メインループ
    On Error GoTo ErrorHandler
    
    For i = 1 To doc.Pages.Count
        Set pg = doc.Pages(i)
        
        ' 【重要】エラーハンドリングのためのコンテキスト更新
        ' どのページで何をしているかを常に変数に保持しておく
        currentContext = "PageIndex: " & i & " | PageName: " & pg.Name
        
        logger.LogInfo "Processing: " & currentContext
        
        ' --- ページ操作のシミュレーション ---
        ' 例: ページサイズを変更する
        ' 意図的にエラーを発生させるためのテスト用ロジック(ページ名が"ErrorPage"なら失敗させる)
        If pg.Name = "ErrorPage" Then
            Err.Raise vbObjectError + 513, "PageOps", "Simulated Failure on Page: " & pg.Name
        End If
        
        ' ページ設定の変更
        With pg.PageSetup
            .PageWidth = 20 ' ここで制約に触れる可能性がある
            .PageHeight = 20
        End With
        ' ----------------------------------
        
    Next i

    logger.LogInfo "Batch Process Completed Successfully."
    MsgBox "全ての処理が正常に完了しました。", vbInformation
    Exit Sub

ErrorHandler:
    ' 3. エラー発生時の救済措置と詳細記録
    ' ここで「どのページで」「何が起きたか」を結合して記録する
    logger.LogError Err.Number, Err.Description, currentContext
    
    ' ユーザーへの通知(詳細なエラー内容はログを見ろ、というスタンス)
    MsgBox "致命的なエラーが発生しました。" & vbCrLf & _
           "原因: " & Err.Description & vbCrLf & _
           "詳細はログファイルを確認してください。" & vbCrLf & _
           "Context: " & currentContext, vbCritical, "Execution Error"
    
    ' リソースの解放(必要に応じて)
    Set pg = Nothing
    Set doc = Nothing
End Sub

4. 実務における設計の勘所

ファイル・データベース連携の注意点

ログをファイルに出力する場合、ネットワークドライブ上の共有フォルダを指定することが多い。しかし、ネットワークの瞬断は避けられない。

  • 堅牢な設計: `clsLogger` 内で、ファイルへの書き込みに失敗した際のリトライ処理、あるいはローカルの一時フォルダへのフォールバック機能を検討せよ。
  • パフォーマンス: 大量(数万件)のシェイプをループで回す際、ループのたびにファイルを開閉(Open/Close)するのは致命的なオーバーヘッドを生む。大量ログが予想される場合は、メモリ上にバッファリングし、一定件数ごとに一括書き出しする設計に昇華させろ。

オブジェクトのライフサイクル管理

エラーが発生した際、`Set pg = Nothing` などの解放処理を忘れてはならない。特に、エラーハンドラー内でさらにエラーが発生する「二重エラー」は、Visioのプロセスをゾンビ化(バックグラウンドで残る)させる最大の原因だ。

結論:エラーハンドリングは「未来の自分」へのギフトである

「エラーが起きないコード」を書こうとするのは、自然界の法則に抗うようなものだ。不可能なことに時間を使うのではなく、「エラーが起きたときに、いかに最短ルートで解決に辿り着けるか」に設計のリソースを割け。

コンテキストを保持したログを出力する。このひと手間が、デバッグ時間を数時間から数秒へと短縮し、君の構築するツールの信頼性を、単なる「便利ツール」から「業務基盤」へと押し上げるのだ。

タイトルとURLをコピーしました