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

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

【極限の知見】Visio VBAを掌握する:ページ操作の「死角」を突く、プロフェッショナルなエラーハンドリング

「マクロを実行したら、エラーが出て止まってしまった……。でも、どのページで、何が原因で止まったのかさっぱりわからない」

Visio VBAで自動化を組んでいると、必ずと言っていいほどこの壁にぶち当たります。特に、ページを大量に生成したり、レイヤーを操作したり、ページ設定を自動書き換えしたりするような「複雑なページ操作」において、エラーは牙を剥きます。

一般的な入門書では「`On Error Resume Next` を使いましょう」なんて書かれていることがありますが、エンジニアとしてそれは絶対に避けてください。 それは、エラーという名の「爆弾」を、爆発するまで見逃し続けているのと同じです。

今日は、マクロの記録から脱却し、現場で「本当に使える」自動化ツールを作れるようになるための、コンテキスト(文脈)を保持したエラーハンドリングの極意を伝授します。

1. なぜ、単なるエラーハンドリングでは足りないのか?

Visioの操作は、Excelのセル操作とは根本的に性質が異なります。Visioは「図形」「レイヤー」「ページ」「ページ設定」という、互いに依存し合う複雑なオブジェクトの集合体です。

例えば、以下のような事態を想像してみてください。

1. 100枚のページを順番に処理するループを開始。
2. 53枚目のページで、レイヤーの追加に失敗。
3. プログラムが停止。

この時、単純なエラーメッセージだけでは、「53枚目のページが、どんな名前で、どんな状態だったのか」が分かりません。これが、大規模な自動化プロジェクトにおける「デバッグの地獄」の正体です。

プロのエンジニアは、エラーが起きた瞬間に「どのオブジェクトが、どのような状態にあったか」というスナップショット(記録)を、ログとして残す設計を行います。

2. 実践:コンテキストを記録するエラーハンドリング・テンプレート

それでは、実際に現場でそのまま使える、堅牢なテンプレートを見ていきましょう。このコードの肝は、「今、自分がどのページを操作しているか」を変数に保持し続け、エラー時にそれを吐き出す点にあります。

Option Explicit

' ======================================================================
' 目的: ページ操作の自動化と、詳細なエラーログの記録
' 構成: メイン処理、エラーハンドラー、ログ出力関数
' ======================================================================

Sub MasterPageAutomation()
    ' --- 1. 変数の定義 ---
    Dim vsoDocument As Visio.Document
    Dim vsoPage As Visio.Page
    Dim i As Integer
    Dim targetPageName As String
    
    ' 現在のコンテキスト(文脈)を保持するための変数
    ' これが「どのページで起きたか」を特定する鍵になります
    Dim currentContext As String
    
    ' エラーハンドリングの開始
    On Error GoTo ErrorHandler
    
    Set vsoDocument = ActiveDocument
    
    ' --- 2. メイン処理のシミュレーション ---
    ' 例として、5枚のページをループで処理し、特定の条件でエラーを発生させます
    For i = 1 To 5
        ' 現在のコンテキストを更新(ログ出力用)
        currentContext = "Loop Index: " & i & " | Page Index: " & i
        
        ' ページを特定(存在しないページにアクセスしようとするとエラーになる設計)
        Set vsoPage = vsoDocument.Pages(i)
        targetPageName = vsoPage.Name
        currentContext = currentContext & " | Page Name: " & targetPageName
        
        ' 【ここでページ操作を実行】
        ' 例: 3枚目のページでわざとエラーを発生させる(存在しないレイヤー操作など)
        If i = 3 Then
            Debug.Print "--- 3枚目のページで意図的なエラーを発生させます ---"
            ' 存在しないプロパティを操作しようとしてエラーを誘発
            vsoPage.PageSetup.PrintScale = 99999 ' 極端な値を設定してエラーを模倣
        End If
        
        ' 通常の操作例: ページ名の変更
        vsoPage.Name = "Processed_Page_" & i
        
        Debug.Print "Successfully processed: " & targetPageName
    Next i

    MsgBox "すべての処理が正常に完了しました。", vbInformation
    Exit Sub

' --- 3. エラーハンドラー ---
ErrorHandler:
    ' エラーが発生した瞬間に、ここへジャンプします
    ' 発生したエラーの詳細と、保持していた「コンテキスト」をログに書き出します
    Call WriteErrorLog(Err, currentContext)
    
    ' ユーザーへの通知(実務では詳細を隠し、ログへの記録を優先する場合もあります)
    MsgBox "致命的なエラーが発生しました。" & vbCrLf & _
           "詳細はログファイルを確認してください。" & vbCrLf & _
           "エラー箇所: " & currentContext, vbCritical, "Automation Error"
End Sub

' --- 4. ログ出力用サブプロシージャ ---
' ログをテキストファイルに書き出す、独立した部品です
Sub WriteErrorLog(errObj As Err, contextInfo As String)
    Dim logFilePath As String
    Dim fileNo As Integer
    
    ' ログファイルのパス(デスクトップに保存する例)
    logFilePath = CreateObject("WScript.Shell").SpecialFolders("Desktop") & "\Visio_Error_Log.txt"
    fileNo = FreeFile
    
    ' ファイルを開いて追記
    Open logFilePath For Append As #fileNo
    
    Print #fileNo, "=================================================="
    Print #fileNo, "Timestamp: " & Now()
    Print #fileNo, "Context:   " & contextInfo ' ここが「どのページか」を示す!
    Print #fileNo, "Error No:  " & errObj.Number
    Print #fileNo, "Description: " & errObj.Description
    Print #fileNo, "=================================================="
    Print #fileNo, ""
    
    Close #fileNo
    
    ' イミディエイトウィンドウにも出力(開発中の確認用)
    Debug.Print "!!! ERROR LOGGED TO: " & logFilePath
End Sub

3. コードの深層解説:なぜこれで「掌握」できるのか

このコードには、初学者が卒業すべき3つの重要な設計思想が組み込まれています。

① コンテキストの変数化 (`currentContext`)

ここが最も重要です。エラーが発生した際、`Err` オブジェクトは「何が起きたか(Description)」は教えてくれますが、「どのページで」は教えてくれません。
そこで、処理の各ステップの直前で `currentContext` という変数に、今から行う操作の情報を詰め込んでおくのです。エラーが起きた瞬間に、この変数には「直前に成功した情報」と「今からやろうとしていた情報」が残っています。

② 責務の分離 (Separation of Concerns)

`MasterPageAutomation`(メイン処理)の中に、ログを書くコードを直接書かないでください。
ログ出力は `WriteErrorLog` という別の部品(サブプロシージャ)に任せます。こうすることで、メインのロジックが汚れず、読みやすくなります。これは、大規模なシステム開発における鉄則です。

③ ライフサイクルへの配慮

Visioのオブジェクト(`vsoPage` など)は、ページが削除されたり、ドキュメントが閉じられたりすると、メモリ上から消滅します。エラーハンドラー内でオブジェクト自体を参照しようとすると、二次エラー(エラーの中でエラーが起きる)が発生して、本当の原因が見えなくなります。
そのため、ログにはオブジェクトそのものではなく、オブジェクトの名前(String型)などの「値」を記録するのが、アーキテクトとしての賢明な判断です。

4. 陥りやすい罠:プロが教える「落とし穴」

最後に、ページ操作における特有の注意点を伝えておきます。

  • 「非表示ページ」の罠: `vsoDocument.Pages(i)` でアクセスしようとしたページが、テンプレートの一部でロックされていたり、非表示設定になっていたりすると、エラーを吐くことがあります。
  • 「削除済みオブジェクト」へのアクセス: ページを削除するループを回すとき、`For i = 1 To Pages.Count` と書くと、ページを消すたびに全体のカウントが減り、インデックスがズレてエラーになります。削除時は「後ろのページから順に(`Step -1`)」処理するのが定石です。
  • メモリの解放: 大量のページを操作する場合、エラーが発生した際に `Set vsoPage = Nothing` のように、オブジェクトへの参照を適切にクリアする癖をつけておくと、メモリリークを防げます。

最後に:ここをクリアすれば、Visio VBAの基本はバッチリですよ

エラーハンドリングを「面倒な作業」と捉えるか、「システムの信頼性を担保する設計」と捉えるか。この違いが、単なる「マクロ作成者」と「業務自動化エンジニア」の分かれ道です。

今回紹介したテンプレートを、まずは自分のコードに組み込んでみてください。エラーが起きたとき、真っ暗な画面の前で立ち尽くすのではなく、ログを見て「あぁ、53枚目のあのページの設定が原因か!」と冷静に分析できるようになるはずです。

その冷静さこそが、エンジニアとしての最大の武器になります。応援していますよ!

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