【極限の知見】MS Project VBAにおけるカスタムフィールド依存関係の解析とグラフ構造化
大規模なエンタープライズ・スケジューリングにおいて、Microsoft Projectのカスタムフィールドは「神の領域」であると同時に、システムの「脆弱性の温床」でもある。
`Text1` に入力された複雑な数式が、`Number5` を参照し、それがさらに `Resource1` の計算に波及する――。この不可視の依存関係(Dependency Chain)を把握せずに数式を書き換える行為は、地雷原を地図なしで歩くことに等しい。数式の一箇所での変更が、プロジェクト全体の計算ロジックを崩壊させ、静かなるデータ汚染を引き起こす。
本稿では、シニアエンジニアおよびシステム管理者が直面するこの「数式のエントロピー増大」に対し、カスタムフィールドの依存関係を抽出し、グラフ構造として可視化するための、極めて実践的かつ低レイヤーなアプローチを解説する。
1. アーキテクチャの設計思想:メタデータの抽出とパース
カスタムフィールドの依存関係を解析するためには、単に `Task.CustomFields` を走査するだけでは不十分だ。我々が解析すべき対象は、`Application.CustomFields` オブジェクトに格納されている `Formula` プロパティ、すなわち「数式そのもの」である。
設計の肝は以下の3層構造にある。
1. Extraction Layer: `Application.CustomFields` から各フィールドの定義(名前、型、数式)を抽出する。
2. Parsing Layer: 正規表現(`VBScript.RegExp`)を用い、数式文字列内から `[Field]` 形式のパターンを特定する。
3. Graph Layer: 抽出した依存関係を「隣接リスト(Adjacency List)」として保持し、グラフ構造を構築する。
ここで重要なのは、オブジェクトのライフサイクル管理だ。VBAはCOM(Component Object Model)の上で動いている。大量のカスタムフィールドをループで処理する際、参照カウントの管理を誤れば、MS Projectプロセスそのものがメモリリークを起こし、最悪の場合、不可解なクラッシュを招く。
2. 実装:依存関係解析エンジン
以下に、依存関係を解析し、結果を解析可能な形式(CSV/テキスト)で出力するコアロジックを示す。ここでは、パフォーマンスとメモリ効率を考慮し、`Scripting.Dictionary` を用いてグラフを構築する。
Option Explicit
' Windows APIを利用した高精度な時間計測(プロファイリング用)
' パフォーマンスのボトルネックを特定するために不可欠
If VBA7 Then
Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long
Else
Declare Function GetTickCount Lib "kernel32" () As Long
End If
''' <summary>
''' カスタムフィールドの依存関係を解析するメインクラス(モジュール)
''' </summary>
Public Sub AnalyzeCustomFieldDependencies()
Dim startTime As Long
startTime = GetTickCount()
Dim app As MSProject.Application
Set app = MSProject.Application
' 解析結果を格納するDictionary (Key: 親フィールド, Value: 子フィールドのCollection)
' Scripting.Dictionaryはメモリ効率と検索速度のバランスに優れる
Dim graph As Object
Set graph = CreateObject("Scripting.Dictionary")
' 正規表現オブジェクトの生成
Dim regEx As Object
Set regEx = CreateObject("VBScript.RegExp")
With regEx
.Pattern = "\[(.*?)\]" ' [Field] 形式をキャプチャするパターン
.Global = True
.IgnoreCase = True
End With
Dim cf As MSProject.CustomField
Dim formula As String
Dim matches As Object
Dim match As Object
Dim targetField As String
On Error GoTo ErrorHandler
' 1. カスタムフィールドの走査
' Application.CustomFields は全カスタムフィールドの集合体である
For Each cf In app.CustomFields
formula = cf.Formula
' 数式が空でない場合のみ解析
If Len(Trim(formula)) > 0 Then
' グラフのノード(親)を初期化
If Not graph.Exists(cf.Name) Then
Set graph(cf.Name) = CreateObject("Scripting.Dictionary")
End If
' 2. 正規表現によるパース
Set matches = regEx.Execute(formula)
For Each match In matches
' match.SubMatches(0) で括弧内の文字列 [Field] を抽出
targetField = match.SubMatches(0)
' 依存先のフィールドを登録
If Not graph(cf.Name).Exists(targetField) Then
graph(cf.Name).Add targetField, True
End If
Next match
End If
Next cf
' 3. 結果の出力 (デバッグコンソールおよびファイルへの書き出し)
OutputGraph graph
' クリーンアップ: 明示的なオブジェクト解放
' COMオブジェクトのライフサイクルを制御し、メモリリークを防止する
Set regEx = Nothing
Set graph = Nothing
Set app = Nothing
Debug.Print "Analysis completed in " & (GetTickCount() - startTime) & " ms."
MsgBox "解析が完了しました。", vbInformation
Exit Sub
ErrorHandler:
MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
' エラー時もリソースを解放する責務がある
Set regEx = Nothing
Set graph = Nothing
End Sub
''' <summary>
''' 構築されたグラフ構造をテキスト形式で出力する
''' </summary>
Private Sub OutputGraph(ByRef graph As Object)
Dim key As Variant
Dim childKey As Variant
Dim output As String
output = "Source,Target" & vbCrLf
For Each key In graph.Keys
' 子要素が存在する場合のみエッジを書き出す
If graph(key).Count > 0 Then
For Each childKey In graph(key).Keys
output = output & key & "," & childKey & vbCrLf
Next childKey
End If
Next key
' 標準出力(イミディエイトウィンドウ)へ
Debug.Print output
' 必要に応じてFileSystemObjectを用いてファイル保存を行う
' ここでは概念を示すに留める
End Sub
3. 深層解説:プロフェッショナルのための最適化テクニック
3.1 COMオブジェクトのメモリ管理
VBAにおいて `Set obj = Nothing` は単なる作法ではない。特に `MSProject.Application` や `Scripting.Dictionary` のような重いCOMオブジェクトを多用する場合、スコープを抜ける前に明示的に解放することは、長時間稼働する自動化サーバーや、大規模なプロジェクトファイルを開き続ける管理者のための「防衛策」である。参照カウントが残存すると、MS Projectのプロセスがバックグラウンドに残り続け、ゾンビプロセス化する原因となる。
3.2 正規表現のコスト
`VBScript.RegExp` は強力だが、ループ内で毎回 `New RegExp` を行うのは愚策だ。本実装のように、ループの外で一度だけインスタンス化し、パターンをプリコンパイル(`.Pattern` の設定)しておくことが、計算量のオーダーを抑える鍵となる。
3.3 依存関係の「循環」への警戒
本ツールは依存関係を抽出するが、実運用においては「循環参照(Circular Reference)」の検知機能を追加すべきだ。`A -> B -> A` というループが存在する場合、Projectの再計算エンジンは無限ループに陥るか、計算結果が不定になる。グラフ理論における「トポロジカルソート」のアルゴリズムを実装することで、循環の有無を数学的に証明することが可能になる。
4. 次なるステップ:可視化の極致へ
本ツールで出力されるCSV形式のデータ(`Source,Target`)は、そのまま Graphviz (dot言語) や PythonのNetworkX に流し込むことができる。
// Graphviz形式の出力例
digraph G {
"Text1" -> "Number1";
"Number1" -> "Text2";
"Text2" -> "Text1"; // 循環参照の検知
}
このように、VBAを「解析エンジン」として使い、可視化を「外部の専門ツール」に委ねる。これこそが、レガシーな環境を現代的なアーキテクチャへと昇華させる、真のエンジニアリングである。
複雑怪奇な数式の迷宮を、論理の光で解き明かせ。
