【実務・中級編】【中級】Shape.CellExistsUによる安全なプロパティ参照:実行時エラーを未然に防ぐ防衛的プログラミング – Visio VBA解析バイブル

スポンサーリンク

【Visio VBA】なぜあなたのマクロは突然落ちるのか?Shape.CellExistsUで実現する「防衛的プログラミング」の極意

開発プロジェクトの現場で、こんな悪夢を見たことはないだろうか。

「何百個ものシェイプを処理する自動レイアウトスクリプトを走らせた。30分後、処理が9割終わったところで突然『実行時エラー ‘-2147352567 (80020009)’: 指定されたセルが見つかりません。』が発生し、マクロが強制終了した。ログも残らず、成果物は中途半端なゴミ図面だけが残された……」

Visio VBA開発において、このエラーは新人から中級者にかけての登竜門であり、同時に多くのシステムを沈没させてきた元凶だ。

原因は単純。「存在しないシェイプデータ(カスタムプロパティ)のセルに、無防備にアクセスした」こと。

今回は、この厄介なエラーを完全に無力化し、プロダクション環境(商用環境)に耐えうる堅牢なVisioマクロを構築するための『Shape.CellExistsU』を活用した防衛的プログラミングの極意を伝授する。

—

なぜ「エラー処理(On Error Resume Next)」に逃げてはいけないのか?

素人がやりがちな最悪のアンチパターンから話を始めよう。エラーを恐れるあまり、以下のようなコードを書くプログラマーがいる。

‘ 【絶対にしてはいけないアンチパターン】
Dim shp As Visio.Shape
For Each shp In ActivePage.Shapes
On Error Resume Next ‘ エラーを無理やり握りつぶす
shp.CellsU(“Prop.Cost”).FormulaU = “=1000”
On Error GoTo 0
Next shp

一見、エラーで止まらないから賢く見えるかもしれない。だが、これはエンジニアリングの観点から言えば「時限爆弾付きの粗大ゴミコード」だ。

On Error Resume Next がもたらす3つの大罪

1. 真の原因隠蔽: スペルミス(例: `Prop.Cst` と書いてしまった等)や、メモリ不足など、本来検知すべき深刻なエラーまで無視してしまう。
2. サイレントバグ(沈黙の故障): 値が入るべきところで入らず、後続の集計処理やDB連携で大惨事を引き起こす。どこで狂ったのかトレースが極めて困難になる。
3. VBAランタイムのパフォーマンス劣化: VBAのエラーハンドリング機構は重い。ループのたびに例外が発生すると、実行速度が劇的に低下する。

我々が目指すべきは、エラーが起きても「耐える」ことではなく、「エラーを絶対に起こさない(事前に防ぐ)」ことだ。そのための唯一にして最強の武器が `Shape.CellExistsU` である。

—

核心:Shape.CellExistsU とは何か?

`Shape.CellExistsU` は、対象のシェイプ内に指定した名前のセルが存在するかどうかを、実行時エラーを発生させずに判定するためのメソッドだ。

‘ 構文
bExists = Shape.CellExistsU(LocaleNameNameOrUniversalName, InheritanceFlag)

  • `LocaleNameNameOrUniversalName`: 探したいセルの名前(例: `”Prop.Cost”` や `”PinX”` など)。
  • `InheritanceFlag`: 継承されたセル(マスターシェイプやスタイルから引き継いだセル)も含めて判定するかどうかのフラグ(通常は `visExistsAnywhere` = 0 を指定)。
  • 戻り値: `Boolean`(存在すれば `True`、なければ `False`)

このメソッドの最大の特徴は、「存在しないセルに怯えてアクセスする前に、存在有無を安全にスイッチングできる」という点にある。これにより、VBAの実行時エラーを完全に回避できる。

—

【実践】プロダクション品質の防衛的プログラミング・テンプレート

実務でそのまま使える、堅牢で保守性の高いモジュールを公開しよう。
このコードは、図面内の全シェイプを巡回し、「Cost(コスト)」というシェイプデータが存在する場合のみ値を書き換え、存在しない場合は自動的に新規作成(あるいはスキップ)する安全設計のロジックだ。

Option Explicit

Public Sub SafeUpdateShapeData()
Dim vsoPage As Visio.Page
Dim vsoShape As Visio.Shape
Dim targetCellName As String

‘ ユニーク名(U suffix版)でセルを指定するのがVisio VBAの鉄則
targetCellName = “Prop.Cost”

‘ アクティブページの取得
Set vsoPage = ActivePage
If vsoPage Is Nothing Then
MsgBox “アクティブなページが存在しません。”, vbCritical
Exit Sub
End If

‘ 画面描画を停止してパフォーマンスを爆発的に向上させる
Application.ScreenUpdating = False

On Error GoTo ErrorHandler

Dim processedCount As Long
processedCount = 0

For Each vsoShape vsoPage.Shapes
‘ グループシェイプのコンテナ自体など、対象外をスキップする判定をここに挟むのも有効
If vsoShape.Type <> visTypeGroup Then

‘ 【防衛的プログラミングの核心】
‘ visExistsAnywhere (0) を指定し、マスターからの継承も含めてセルの存在をチェック
If vsoShape.CellExistsU(targetCellName, visExistsAnywhere) Then
‘ セルが存在するので、安全に値を代入
vsoShape.CellsU(targetCellName).FormulaU = “5000”
processedCount = processedCount + 1
Else
‘ セルが存在しない場合のフォールバック処理
‘ 必要であればここで動的にセルの追加を行うことも可能
‘ Call AddCustomProperty(vsoShape, targetCellName)
End If

End If
Next vsoShape

Application.ScreenUpdating = True
MsgBox “処理が完了しました。更新されたシェイプ数: ” & processedCount, vbInformation
Exit Sub

ErrorHandler:
Application.ScreenUpdating = True
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical
End Sub

チーフアーキテクトからの設計上のアドバイス

1. CellsU と Cells の使い分け:
コード内では必ず `CellsU` および `CellExistsU` を使用すること。末尾の `U` は Universal(言語非依存のユニーク名)を意味する。`Cells`(ロケール依存)を使うと、日本語版Visioと英語版Visioの間でマクロが動かなくなるという地獄を見る。プロフェッショナルは常に `U` 付きを使う。
2. ScreenUpdating の制御:
何千個ものシェイプを操作する際、画面描画を有効にしたままだとVisioが毎フレーム再描画を試みてフリーズ寸前になる。必ず `Application.ScreenUpdating = False` で描画を止め、処理終了時に `True` に戻すこと。

—

外部データ(Excel・DB)連携における必須防御策

この `CellExistsU` の真価が発揮されるのは、ExcelやデータベースからインポートしたデータをVisioのシェイプに流し込む自動化ツールを開発する時だ。

外部データとVisioを紐付ける際、以下のような不整合が日常茶飯事でおきる。

  • 「設計者が勝手にVisio上のシェイプのプロセパティ名を消去・変更した」
  • 「データベース側のスキーマが変わり、対応するプロパティが存在しない」

こうした外部要因によるクラッシュを防ぐため、データバインディング層には必ず以下の防衛ロジックを組み込むべきだ。

‘ 外部データレコードをシェイプに安全に流し込む関数例
Public Sub BindDataToShape(ByRef vsoShape As Visio.Shape, ByVal propName As String, ByVal dataValue As String)
Dim fullCellName As String
fullCellName = “Prop.” & propName

‘ セルが存在するか厳密にチェック
If vsoShape.CellExistsU(fullCellName, visExistsAnywhere) Then
‘ 文字列として安全に代入(数式インジェクションを防ぐためダブルクォーテーションで囲む処理等も考慮)
vsoShape.CellsU(fullCellName).ResultStr(Visio.VisUnitCodes.visNoCast) = dataValue
Else
‘ デバッグログに出力する、あるいは動的にプロパティを追加する等のハンドリング
Debug.Print “警告: シェイプ (ID: ” & vsoShape.ID & “) にプロパティ ” & fullCellName & ” が存在しないためスキップしました。”
End If
End Sub

この設計を取り入れるだけで、外部データの揺らぎによってマクロが止まることは二度となくなる。保守担当者から感謝されるコードベースになるはずだ。

—

まとめ

Visio VBA開発において、オブジェクトの存在を事前に確認する「防衛的プログラミング」は、選択肢ではなくプロとしての必須教養である。

  • `On Error Resume Next` に逃げるな。 バグを隠蔽するコードは悪だ。
  • `CellExistsU` を使え。 アクセスする前にセルの生死を確認し、実行時エラーを未然に根絶せよ。
  • `CellsU` を使え。 グローバルな環境変化に強い、言語非依存のコードを書け。

この極意をマスターしたあなたなら、もう何千個のシェイプを相手にしようとも、途中で止まる恐怖に怯える必要はない。圧倒的な堅牢性を誇る、真に実用的な自動化ツールを作り上げてほしい。

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