執行猶予なきVisio開発からの脱却:`CellExistsU`がもたらす真の防衛的アーキテクチャ
Visio VBAの開発現場において、最も生産性を殺し、コードベースを腐敗させる元凶は何か。それは、存在しないShapeSheetセルへのアクセスが生む「実行時エラー(エラー番号:-2147352567 / 0x80020009)」である。
ステンシルから無造作にドラッグ&ドロップされたシェイプ、外部システムからインポートされた異形のSVG、あるいはバージョン違いのカスタムステンシル。これらが混在する環境において、`.CellsU(“Prop.Cost”)`といった直叩きコードは、地雷原を目隠しで歩くようなものだ。
今回は、Visioのオブジェクトモデルの深層を知り尽くしたアーキテクトの視点から、`Shape.CellExistsU`を用いた無敗の防衛的プログラミングと、大規模図面におけるメモリ・パフォーマンス最適化の極意を授ける。
—
1. なぜ `.CellsU` の直叩きは悪手なのか?
多くの初中級プログラマは、エラーハンドリングに`On Error Resume Next`を使う。しかし、これはプログラミングの敗北宣言に他ならない。
‘ 【アンチパターン】絶対にやってはならない実装
On Error Resume Next
Dim costValue As Double
costValue = shp.CellsU(“Prop.Cost”).ResultIU
On Error GoTo 0 ‘ エラーを握りつぶすな!
このアプローチがもたらす致命的な弊害
1. 暗黙のパフォーマンス低下: VBAの例外機構は重い。存在しないセルにアクセスするたびにCOMの境界を跨ぐ例外が発生し、数千個のシェイプを持つ図面では処理が数倍から数十倍に跳ね上がる。
2. バグの隠蔽: `Prop.Cost`が存在しないのか、それともセルの値の取得時に別の予期せぬエラー(型不一致など)が起きたのかを判別できなくなる。
3. レガシー環境でのメモリリーク: エラーコンテキストがVBAのスタックに残ることで、長期稼働する自動化プロセスにおいてメモリフットプリントが肥大化する原因となる。
真のエンジニアは、エラーが発生してから対処するのではなく、「実行前に存在を確定させる」。そこで登場するのが `CellExistsU` メソッドである。
—
2. `CellExistsU` の正確なセマンティクスとオーバーヘッドの制御
`Shape.CellExistsU` は、指定されたセルの名前(Universal名)がシェイプのShapeSheetに存在するかどうかを整数(`Integer` / `VisCellExistFlags`)で返す。
‘ 構文の基本
Dim isExist As Integer
isExist = shp.CellExistsU(CellNameUID, fExistsFlags)
ここで重要なのが第2引数 `fExistsFlags` の指定だ。ここを適当に済ませているエンジニアは、深いレベルでVisioを理解していないと断言できる。
| フラグ定数 (VisCellExistFlags) | 値 | 意味・実務での使い分け |
| :— | :— | :— |
| `visExistsAnywhere` | 0 | セルがローカル、またはマスター(シェイプマスター)のどこかに存在すれば真を返す。(通常はこれを使う) |
| `visExistsLocally` | 1 | マスターへの継承を無視し、そのインスタンス(ローカル)にセルが明示的に存在する場合のみ真を返す。 |
チーフアーキテクトの知見:無駄なCOMラウンドトリップを避ける
`CellExistsU` 自体もCOMのメソッド呼び出しであるため、同一シェイプに対して何度も呼び出すのは愚かである。もし複数のカスタムプロパティを検証する場合は、事前に存在確認の結果をローカル変数にキャッシュするか、一括処理の設計に落とし込むべきだ。
—
3. 実装パターン:堅牢性と速度を両立する防衛的プログラメント
以下に、実業務のシステム連携や大規模図面の一括パースにおいて耐えうる、実用的なVBAモジュールを示す。このコードは、エラーを一切発生させず、かつメモリ効率を極限まで高めたものである。
Option Explicit
‘ ==============================================================================
‘ 担当: チーフアーキテクト
‘ 概要: 指定されたシェイプ群から安全にカスタムプロパティを抽出し、
‘ 外部システム(DB/JSON)連携用の構造体にマッピングする
‘ ==============================================================================
Public Sub ExportShapePropertiesToLog()
Dim doc As Visio.Document
Set doc = ActiveDocument
Dim page As Visio.Page
Set page = doc.ActivePage
Dim shp As Visio.Shape
Dim targetCount As Long
targetCount = page.Shapes.Count
‘ ログ出力用のバッファ(大量の文字列結合によるメモリフラグメントを防ぐため、必要に応じ配列やADODB.Streamを検討)
Debug.Print “— 処理開始: シェイプ総数 = ” & targetCount & ” —”
Dim i As Long
For i = 1 To targetCount
Set shp = page.Shapes(i)
‘ 1. シェイプがガイドや隠しレイヤー等、処理対象外であるかを判定
If Not shp.Master Is Nothing Then
‘ マスターシェイプ自体のインスタンスかどうかの判定も含める
End If
‘ 2. “Prop.AssetID” が安全に存在するかチェック
If shp.CellExistsU(“Prop.AssetID”, visExistsAnywhere) <> 0 Then
‘ 3. 存在する場合は安全に値を取得
Dim assetId As String
assetId = shp.CellsU(“Prop.AssetID.Value”).ResultStr(“”)
‘ 4. 値の存在チェック(空文字対策)
If Len(assetId) > 0 Then
‘ 5. さらに別のプロパティ “Prop.Status” があるか動的に確認
Dim currentStatus As String
If shp.CellExistsU(“Prop.Status”, visExistsAnywhere) <> 0 Then
currentStatus = shp.CellsU(“Prop.Status”).ResultStr(“”)
Else
currentStatus = “Unspecified”
End If
‘ 処理の実行
Debug.Print “Found Asset: ID = ” & assetId & “, Status = ” & currentStatus
End If
End If
‘ 【重要】ループ内でのオブジェクト解放の作法
‘ 変数のスコープを意識し、次のループで確実に参照を上書きする
Set shp = Nothing
Next i
‘ 明示的なオブジェクト解放
Set page = Nothing
Set doc = Nothing
Debug.Print “— 処理終了 —”
End Sub
—
4. レガシー環境とシステム間連携における実戦的プラクティス
長年運用されてきたVisio図面は、組織の変遷や担当者の変更に伴い、「ゴミ」のような不正なShapeSheet構造を孕んでいることが多い。特にAutoCADからのインポート図面や、古いVisio (Visio 2003/2007形式の `.vsd`) から `.vsdm` へマイグレーションされた図面では、セルのデータ型が破損しているケースすらある。
ここで、システム間連携(例:C# (.NET) によるVisioアドインや外部COMコンポーネントからの操作)における知見を共有する。
.NET (C#) からの `CellExistsU` 呼び出しの注意点
VB.NETやC#からVisio Interop (`Microsoft.Office.Interop.Visio`) を経由して `CellExistsU` を叩く場合、COM例外のハンドリングはVBA以上にシビアになる。
// C#での防衛的実装例
public void ProcessShape(Visio.Shape shape)
{
// COMオブジェクトの解放を考慮したスコープ管理
try
{
// ユニバーサル名で安全にチェック
// ※ C#では戻り値は short (Int16) または boolとして扱われる場合があるため注意
if (shape.CellExistsU(“Prop.TargetSystemCode”, (short)Visio.VisCellExistFlags.visExistsAnywhere) != 0)
{
Visio.Cell cell = shape.CellsU[“Prop.TargetSystemCode”];
string systemCode = cell.ResultStr[Visio.VisUnitCodes.visNoCast];
// メモリリークを防ぐため、取得したCOMオブジェクトは即座に解放する
System.Runtime.InteropServices.Marshal.ReleaseComObject(cell);
// ビジネスロジックの実行
SyncWithExternalApi(systemCode);
}
}
catch (System.Runtime.InteropServices.COMException ex)
{
// Visioエンジン内部起因のCOM例外へのフォールバック
System.Diagnostics.Debug.WriteLine($”COM Error: {ex.ErrorCode} – {ex.Message}”);
}
}
※ チーフアーキテクトの戒め: .NET環境では、`shape.CellsU[…]` のようなプロパティアクセスは背後でRCW (Runtime Callable Wrapper) を生成する。ループ内でこれを解放し忘れると、GC(ガベージコレクション)が追いつかずにVisioプロセスがメモリリークでクラッシュする。C#で書く場合は、取得した `Cell` オブジェクトは必ず個別に `Marshal.ReleaseComObject` を呼ぶか、変数のライフサイクルを局所化しなければならない。
—
5. 総括:アーキテクトが目指すべき境地
`Shape.CellExistsU` は、単なる「エラー回避のためのメソッド」ではない。
それは、「予測不能な外部入力やレガシーデータに対して、システムが理路整然と立ち振る舞うための防衛ライン」である。
現場のプログラマが安易な `On Error Resume Next` に逃げるのを許さず、すべての例外要因をコードの構造(ガード・クローズ)によって事前に無力化する。この規律をチーム全体に徹底することこそが、保守性ゼロのスパゲッティVBAを、10年耐えうる堅牢な社内基幹システムへと昇華させる唯一の道である。
図面の海に潜む不確定要素を、あなたの手で完全に制御下に置け。
