【Visio VBA極限講義】マスターシェイプの曖昧検索:現場で「使える」コードと「壊れる」コードの境界線
業務自動化の現場において、「特定のパーツを探して配置する」という動作は、一見すると単純な作業に見える。しかし、プロフェッショナルなエンジニアとして、この「単純な作業」を実装する際に、君はどのような設計思想を持つだろうか?
初心者は、単に `For Each` でループを回し、`If Name Like “*keyword*” Then` と書くだけで満足する。だが、それは「動くだけのコード」だ。実務におけるツールは、「想定外の入力」や「環境の変化」に耐えうる堅牢性(Robustness)がなければ、現場の信頼を失い、保守コストという名の負債を積み上げる。
本稿では、ステンシル内のマスターシェイプを名前の一部で曖昧検索し、ドキュメント上にドロップするロジックを題材に、「プロダクション品質」のコードとは何かを徹底的に解説する。
1. なぜ、単純なループ実装では不十分なのか
まず、多くの開発者が陥る「アンチパターン」を整理しておこう。
アンチパターン:場当たり的な実装
' 典型的な「動くだけ」の悪い例
Sub BadExample()
Dim mst As Master
For Each mst In Application.Stencils(1).Masters ' ステンシル番号の直指定は危険
If mst.Name Like "*Server*" Then
ActivePage.Drop mst, 1, 1
Exit Sub
End If
Next
End Sub
このコードには、プロフェッショナルとして見過ごせない致命的な欠陥が3つある。
1. オブジェクト参照の脆弱性: `Stencils(1)` のようにインデックスを直指定するのは、ユーザーがステンシルを開閉した瞬間にクラッシュする原因となる。
2. 例外処理の欠如: 指定した名前のマスターが見つからなかった場合、あるいはステンシルが読み取り専用だった場合に、ユーザーに無慈悲なランタイムエラーを突きつける。
3. 検索ロジックの硬直性: 大文字・小文字の区別や、複数の候補が見つかった場合のハンドリングが考慮されていない。
2. 設計思想:堅牢な検索エンジンの要件
実務で通用する検索ロジックを設計する場合、以下の3点を設計指針(Design Principles)に据えるべきだ。
① 検索対象の明示化(Explicit Scope)
「すべてのステンシル」を探すのか、「現在開いている特定のステンシル」を探すのか。これを曖昧にしてはいけない。本稿では、「現在開いているすべてのステンシル」をスキャン対象とする、汎用性の高い設計を採用する。
② 失敗を前提としたエラーハンドリング
「見つからないこと」はエラーではない。それは「検索結果がゼロである」という正常な状態(Valid State)だ。プログラムを異常終了させるのではなく、呼び出し元に結果を適切に返す設計にする。
③ 結合度の低減(Decoupling)
「検索する機能」と「ドロップする機能」を分離せよ。検索ロジックが「ドロップ」という副作用(Side Effect)を直接持つと、ユニットテストが困難になり、再利用性が著しく低下する。
3. プロダクション・コード:マスターシェイプ検索エンジン
以下に、私が設計する「実務用テンプレート」を示す。このコードは、単なる検索ではなく、「検索結果のオブジェクトを返す」という責務に特化している。
''
' @module: ShapeSearchEngine
' @description: マスターシェイプの高度な検索および配置制御
''
Option Explicit
''' <summary>
''' ステンシル内から部分一致でマスターシェイプを検索する
''' </summary>
''' <param name="partialName">検索キーワード</param>
''' <returns>一致した最初のMasterオブジェクト。見つからない場合はNothing</returns>
Public Function FindMasterByPartialName(ByVal partialName As String) As Visio.Master
Dim stenc As Visio.Stencil
Dim mst As Visio.Master
' 入力値のバリデーション
If Trim(partialName) = "" Then
Debug.Print "Error: Search keyword is empty."
Set FindMasterByPartialName = Nothing
Exit Function
End If
On Error GoTo ErrorHandler
' 全ての開いているステンシルを走査
' ※Application.Stencilsは、現在開いているステンシルセットを管理している
For Each stenc In Application.Stencils
For Each mst In stenc.Masters
' InStr関数を用い、大文字小文字を区別せずに検索 (vbTextCompare)
' Like演算子よりも、意図的な比較制御が容易である
If InStr(1, mst.Name, partialName, vbTextCompare) > 0 Then
Set FindMasterByPartialName = mst
Exit Function ' 最初に見つかったものを返す(設計方針により変更可)
End If
Next mst
Next stenc
' 見つからなかった場合
Set FindMasterByPartialName = Nothing
Exit Function
ErrorHandler:
' ログ出力(実務ではファイルやDBへ記録すべき)
Debug.Print "Critical Error in FindMasterByPartialName: " & Err.Description
Set FindMasterByPartialName = Nothing
End Function
''' <summary>
''' 検索したマスターを現在のページに配置する
''' </summary>
''' <param name="keyword">検索キーワード</param>
''' <param name="x">配置X座標</param>
''' <param name="y">配置Y座標</param>
Public Sub DropMasterByKeyword(ByVal keyword As String, ByVal x As Double, ByVal y As Double)
Dim targetMaster As Visio.Master
Dim droppedShape As Visio.Shape
' 1. 検索ロジックの呼び出し(責務の分離)
Set targetMaster = FindMasterByPartialName(keyword)
' 2. 結果の判定
If targetMaster Is Nothing Then
MsgBox "キーワード「" & keyword & "」に一致するマスターは見つかりませんでした。", vbExclamation, "検索失敗"
Exit Sub
End If
' 3. 配置処理
On Error GoTo DropError
Set droppedShape = ActivePage.Drop(targetMaster, x, y)
' 配置成功時のフィードバック(必要に応じて)
Debug.Print "Successfully dropped: " & droppedShape.Name
Exit Sub
DropError:
MsgBox "シェイプの配置に失敗しました。ページの状態を確認してください。", vbCritical, "配置エラー"
End Sub
4. アーキテクトの視点:さらに高みを目指すために
このコードは、一般的な業務ツールとしては十分な品質を備えている。しかし、大規模なエンタープライズ・システムへとスケールさせる場合、君はさらに以下の検討を行う必要がある。
データベース(DB)との連携
マスターシェイプの名前は、ユーザーの誤操作やステンシルの更新によって容易に変更される。
「名前で検索する」というロジックを、「DB上のID(管理番号)とマスターの隠しデータ(Custom Properties)を紐付ける」方式に昇華させよ。
- マスターに `ID_Property` というプロパティを持たせる。
- VBAはDBから「キーワード $\to$ ID」を取得し、そのIDを持つマスターを検索する。
これにより、名前が変わっても自動化ツールが壊れない、極めて堅牢なシステムが構築できる。
パフォーマンスの最適化
数千個のマスターを持つ巨大なステンシルを扱う場合、`For Each` のループは無視できないコストになる。
もし検索頻度が高いのであれば、アプリケーション起動時にマスターの「名前とオブジェクトへの参照」を Dictionaryオブジェクト(連想配列)にキャッシュしておく設計を検討せよ。計算量は $O(n)$ から $O(1)$ へと劇的に改善される。
結論
「動くコード」を書くのは誰にでもできる。しかし、「保守でき、拡張でき、壊れにくいコード」を書ける者こそが、真のエンジニアだ。
今回紹介した `FindMasterByPartialName` のように、「検索」という単一の責務に集中させ、エラーを制御し、呼び出し元に結果を明示的に返す。 この当たり前の積み重ねが、君の作成するツールの価値を決定づける。
さあ、コードを書き直せ。美しく、そして強靭な設計へと。
