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

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

[Architect’s Journal] Visio VBAにおける「沈黙の失敗」を打破せよ:コンテキストを保持する堅牢なエラーハンドリング設計

業務自動化の現場において、ツールが「動く」ことと「信頼に値する」ことの間には、深くて暗い川が流れている。

多くの開発者が、Visio VBAでページ操作やレイヤー操作の自動化を行う際、手っ取り早く `On Error Resume Next` を書き込む。しかし、これはプロフェッショナルの仕事ではない。それは、バグを「解決」しているのではなく、単に「隠蔽」しているだけだ。

複雑な図面、数百枚のページ、動的に生成されるレイヤー。これらを扱う大規模な自動化処理において、エラーが発生した瞬間に処理が停止してしまえば、ユーザーの作業を中断させる。かといって、エラーを無視して進めば、不完全な図面が出来上がり、後工程で致命的な問題を引き起こす。

我々アーキテクトに求められるのは、「エラーを検知し、詳細な文脈(コンテキスト)を記録し、かつ致命的な停止を避けつつ、処理を継続させる」という、高度なフォールトトレランス(耐故障性)を備えた設計である。

本稿では、Visio VBAにおける「スタックトレース風ログ出力」と「ページスキップ戦略」を実装するための、極限の知見を伝授する。

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

VBAの標準的な `Err.Number` と `Err.Description` だけでは、実務レベルのトラブルシューティングには到底足りない。なぜなら、Visioのオブジェクトモデルは階層的であり、以下の情報が欠落しているからだ。

  • 「どのページ」の操作中に発生したのか?
  • 「どのレイヤー」に対して操作を行おうとしていたのか?
  • 「どのステップ(追加、削除、サイズ変更)」で失敗したのか?

例えば、「ページのリサイズに失敗しました(Error 0x800…)」というログだけ渡されても、エンジニアは数千枚のページを一つずつ手作業で確認することになる。これは、自動化ツールの存在意義を根底から覆す「非効率の極み」である。

2. 設計思想:コンテキスト・アウェア・ハンドリング

我々が実装すべきは、「Context-Aware Error Handling(文脈を理解するエラーハンドリング)」である。

エラーが発生した際、その時点の「現在の状態(State)」をログにパッケージ化して出力する。具体的には、以下の情報を一つのエラーレコードとして統合する設計をとる。

1. Timestamp: 発生時刻
2. Operation: 実行中だったメソッド名(例: `ResizePage`)
3. Target Identity: 対象のページ名、ページID、またはレイヤー名
4. Error Details: Errオブジェクトの数値とメッセージ
5. Stack Trace (Simulated): どの関数からどの関数が呼ばれていたかの軌跡

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

以下に、ページ操作のバッチ処理において、失敗したページをスキップしつつ、詳細なログを残す堅牢な設計パターンを示す。

Option Explicit

' ===========================================================================
' Error Context Structure
' エラー発生時のコンテキストを保持するための定数
' ===========================================================================
Private Enum OpType
    opPageAdd = 1
    opPageDelete = 2
    opPageResize = 3
    opLayerConfig = 4
End Enum

' ===========================================================================
' Main Entry Point: ページ一括処理エンジン
' ===========================================================================
Public Sub ExecuteRobustPageAutomation()
    Dim vsoDoc As Visio.Document
    Set vsoDoc = ThisDocument
    
    Dim pg As Visio.Page
    Dim targetPageNames As Variant
    Dim i As Long
    
    ' テスト用のターゲットリスト(実務ではDBやExcelから取得)
    targetPageNames = Array("Page-1", "Invalid_Page", "Page-3", "Error_Trigger_Page")

    Debug.Print "--- Automation Started: " & Now & " ---"

    ' ページループ処理
    For i = LBound(targetPageNames) To UBound(targetPageNames)
        ' -------------------------------------------------------------------
        ' ページ単位のスコープでエラーハンドリングを行う。
        ' これにより、一つのページが失敗しても、次のページへのループを継続できる。
        ' -------------------------------------------------------------------
        On Error GoTo PageErrorScope
        
        ' 1. ターゲットページの特定(存在しない場合の対策)
        Set pg = FindPageByName(vsoDoc, CStr(targetPageNames(i)))
        
        ' 2. ページ操作の実行(コンテキストを渡す)
        ProcessSinglePage pg, OpType.opPageResize
        
        Debug.Print "Successfully processed: " & targetPageNames(i)

ContinueNextPage:
        On Error GoTo 0 ' ハンドラをリセット
        GoTo NextIteration

PageErrorScope:
        ' エラー発生時、詳細なコンテキストとともにログを出力
        ' ここではエラーを「記録」することに徹し、処理を中断させない
        LogErrorDetailed targetPageNames(i), OpType.opPageResize, Err.Number, Err.Description
        Resume ContinueNextPage

NextIteration:
    Next i

    Debug.Print "--- Automation Completed: " & Now & " ---"
    MsgBox "処理が完了しました。詳細はログを確認してください。", vbInformation
End Sub

' ===========================================================================
' Core Logic: 個別ページの操作ロジック
' ===========================================================================
Private Sub ProcessSinglePage(ByRef targetPg As Visio.Page, ByVal currentOp As OpType)
    ' 引数がNothingの場合、即座にエラーを発生させて呼び出し元に制御を戻す
    If targetPg Is Nothing Then Err.Raise vbObjectError + 513, "ProcessSinglePage", "Target page is Nothing."

    ' --- 擬似的な操作フロー ---
    ' 1. ページサイズの変更
    ' 実際にはここで行われる操作が複雑になる
    Select Case currentOp
        Case OpType.opPageResize
            ' 特定の条件下でエラーをシミュレート(テスト用)
            If targetPg.Name = "Error_Trigger_Page" Then Err.Raise vbObjectError + 999, "ResizeLogic", "Simulated hardware failure."
            
            targetPg.PageSheet.CellsU("PageWidth").FormulaU = "15 in"
            targetPg.PageSheet.CellsU("PageHeight").FormulaU = "10 in"
            
        Case OpType.opPageAdd
            ' 追加ロジック...
    End Select
End Sub

' ===========================================================================
' Helper: ページ検索(堅牢な検索)
' ===========================================================================
Private Function FindPageByName(ByRef doc As Visio.Document, ByVal name As String) As Visio.Page
    Dim pg As Visio.Page
    For Each pg In doc.Pages
        If pg.Name = name Then
            Set FindPageByName = pg
            Exit Function
        End If
    Next pg
    ' 見つからない場合はNothingを返し、呼び出し元でエラーハンドリングさせる
    Set FindPageByName = Nothing
End Function

' ===========================================================================
' Logging Engine: 詳細ログ出力
' ===========================================================================
Private Sub LogErrorDetailed(ByVal contextName As String, ByVal op As OpType, ByVal errNum As Long, ByVal errDesc As String)
    Dim logMsg As String
    Dim opName As String
    
    ' 列挙型を文字列に変換
    Select Case op
        Case OpType.opPageAdd: opName = "PAGE_ADD"
        Case OpType.opPageDelete: opName = "PAGE_DELETE"
        Case OpType.opPageResize: opName = "PAGE_RESIZE"
        Case OpType.opLayerConfig: opName = "LAYER_CONFIG"
        Case Else: opName = "UNKNOWN_OP"
    End Select

    ' スタックトレースを模した構造化ログの構築
    ' 実務ではこれをファイル出力やデータベースへのINSERTに置き換える
    logMsg = String(50, "=") & vbCrLf & _
             "[ERROR REPORT]" & vbCrLf & _
             "Timestamp   : " & Now & vbCrLf & _
             "Target      : " & contextName & vbCrLf & _
             "Operation   : " & opName & vbCrLf & _
             "Error No    : " & errNum & vbCrLf & _
             "Description : " & errDesc & vbCrLf & _
             String(50, "=")

    ' 1. イミディエイトウィンドウへの出力(開発用)
    Debug.Print logMsg
    
    ' 2. 【実務アドバイス】ファイルへの書き出し
    ' AppendToFile "C:\Logs\VisioAutomation_Log.txt", logMsg
End Sub

4. アーキテクトの視点:実務における運用上の注意点

このコードをプロダクション環境に投入する際、以下の3点を必ず考慮してください。

① 外部連携(File/DB)の排他制御

ログをテキストファイルに書き出す場合、複数のインスタンスが同時に走ると「ファイルが他のプロセスで使用されています」というエラーで、ログ出力自体が失敗するという本末転倒な事態が起こります。

  • 対策: ログ出力専用のクラスを作成し、ファイルを開く際は `Append` モードを使用すること。あるいは、ログを一旦メモリ上のコレクションに蓄積し、全処理終了後に一括書き出しを行う設計にしてください。

② 状態の整合性(State Integrity)

ページのリサイズ中にエラーが発生した場合、そのページは「中途半端なサイズ」になっている可能性があります。

  • 対策: エラーが発生したページに対して、ロールバック(元のサイズに戻す)を行うか、あるいは「異常なページ」としてフラグを立て、後続のプロセスから隔離するロジックを検討してください。

③ パフォーマンスの重み

`On Error GoTo` は、エラーが発生しない限りオーバーヘッドは極めて小さいですが、エラーが発生した瞬間にスタックのジャンプが発生します。

  • 対策: ループの「外側」ではなく、必ず「ループの内側(個別のオブジェクト操作単位)」にエラーハンドリングを配置してください。これにより、エラーの局所化(Isolation)が可能になります。

結論

優れた自動化エンジニアは、コードの「成功ルート」を書くことよりも、「失敗ルート」をいかに美しく、かつ詳細に設計することに情熱を注ぎます。

`On Error Resume Next` という安易な逃げ道を捨て、コンテキストを伴うログ設計を導入すること。それが、あなたの作成するツールを「単なるスクリプト」から「プロフェッショナルな業務アプリケーション」へと昇華させる唯一の道です。

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