【テクニカル・上級編】【実務中級】Taskの「Predecessors」文字列を解析し、依存関係をグラフ化するためのデータ抽出法 – Project VBA解析バイブル

スポンサーリンク

Project VBAを掌握する極限の知見:Taskの「Predecessors」文字列を解析し、依存関係をグラフ化するデータ抽出法

Microsoft ProjectのVBA開発において、オブジェクトモデルの挙動、特にタスク間の依存関係(Predecessors / Successors)の解析は、多くの開発者が挫折するポイントの一つである。GUI上では直感的に一本の線で結ばれている先行タスク・後続タスクの関係も、COMの裏側では単なる「不整形な文字列(Comma-delimited string with various dependency types and lag)」として保持されている。

本稿では、MS Projectの奥底に眠る`Task.Predecessors`プロパティの文字列構造を完全分解し、Graphviz等の外部グラフ描画エンジンへ渡すためのエッジリスト(Edge List)を、極限まで最適化されたメモリ管理と高速処理で抽出する手法を解説する。

1. MS Projectオブジェクトモデルの闇:`Predecessors`文字列の構造

初心者は`Task.Predecessors`がタスクIDの単純なカンマ区切りだと勘違いする。だが、実務で使われるスケジュールにおいて、このプロパティは以下のような多様な修飾子を含んだ「方言」の塊として返ってくる。

  • `3` (単一の先行タスク:終了-開始/FS)
  • `3FS+2d` (ラグ付き)
  • `4SS, 5FF-1d` (複数先行、かつ異なる依存関係タイプ)
  • `ExternalTask.mpp\12` (外部プロジェクトへのリンク)

これをVBAの文字列操作関数(`Split`や`Mid`)だけで愚直にパースしようとすると、スパゲッティコードが完成するだけでなく、大規模プロジェクト(数万タスク)においてCOMのラウンドトリップオーバーヘッドにより実行時間が数分単位で膨れ上がる。

シニアエンジニアが取るべきアプローチは、正規表現(VBScript.RegExp)によるトークン化と、Variant配列を活用したメモリ上のインメモリ・グラフ構築である。

2. 実装:依存関係抽出エンジン(VBAコード)

以下のコードは、アクティブなProjectからすべてのタスクと依存関係を走査し、GraphvizのDOT言語形式(有向グラフ)へ変換するためのデータを出力する実用モジュールである。

Option Explicit

‘ —————————————————————–
‘ 建築・大規模プロジェクト対応 依存関係グラフ抽出エンジン
‘ Architecture & Performance Optimized for MS Project VBA
‘ —————————————————————–
Public Sub ExportTaskDependenciesToGraphviz()
Dim prj As Project
Set prj = ActiveProject

‘ 巨大なプロジェクトにおける画面描画停止による高速化(COMラウンドトリップの抑制)
Application.ScreenUpdating = False

Dim t As Task
Dim colEdges As Collection
Set colEdges = New Collection

Dim regEx As Object
Set regEx = CreateObject(“VBScript.RegExp”)
‘ タスクID、依存関係タイプ(FS, SS, FF, SF)、ラグを抽出する正規表現
‘ 例: “12FS+3d” から ID(12), Type(FS), Lag(+3d) をキャプチャ
regEx.Pattern = “^(?:.\\)?([0-9]+)([A-Z]{0,2})([+-].)?$”
regEx.IgnoreCase = True
regEx.Global = False

Dim startTime As Double
startTime = Timer

On Error GoTo ErrorHandler

‘ 1. タスクコレクションの走査と依存関係の解析
Dim rawPredecessors As String
Dim preds() As String
Dim i As Long
Dim matchColl As Object
Dim targetID As String
Dim depType As String
Dim lagStr As String

For Each t In prj.Tasks
‘ 存在しないタスク(マイルストーンのプレースホルダー等)やサマリーの誤認を防ぐ
If Not t Is Nothing Then
If Not t.Summary And t.Text1 <> “DELETED” Then
rawPredecessors = t.Predecessors

If Len(rawPredecessors) > 0 Then
‘ カンマ区切りの先行タスク群を分割
preds = Split(rawPredecessors, “,”)

For i = LBound(preds) To UBound(preds)
Dim cleanPred As String
cleanPred = Trim(preds(i))

If regEx.Test(cleanPred) Then
Set matchColl = regEx.Execute(cleanPred)
targetID = matchColl(0).SubMatches(0)
depType = UCase(matchColl(0).SubMatches(1))
If depType = “” Then depType = “FS” ‘ デフォルトはFS

lagStr = matchColl(0).SubMatches(2)

‘ エッジ情報を作成 (先行ID, 後続ID, 属性)
Dim edgeInfo As String
edgeInfo = targetID & ” -> ” & t.ID & ” [label=””” & depType & IIf(Len(lagStr) > 0, lagStr, “”) & “””];”
colEdges.Add edgeInfo
End If
Next i
End If
End If
End If
Next t

‘ 2. Graphviz (DOT言語) 形式でのファイル出力
Call WriteDotFile(colEdges, prj.Path & “\project_dependency.dot”)

Application.ScreenUpdating = True
MsgBox “依存関係の抽出が完了しました。” & vbCrLf & _
“処理時間: ” & Format(Timer – startTime, “0.00秒”) & vbCrLf & _
“検出エッジ数: ” & colEdges.Count, vbInformation, “Graphviz Exporter”
Exit Sub

ErrorHandler:
Application.ScreenUpdating = True
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical
‘ オブジェクトの明示的解放
Set regEx = Nothing
Set colEdges = Nothing
End Sub

Private Sub WriteDotFile(ByRef edges As Collection, ByVal filePath As String)
Dim fileNum As Integer
fileNum = FreeFile

Open filePath For Output As #fileNum
Print #fileNum, “digraph ProjectDependencies {”
Print #fileNum, ” rankdir=LR;”
Print #fileNum, ” node [shape=box, style=filled, fillcolor=lightblue];”

Dim edge As Variant
For Each edge In edges
Print #fileNum, ” ” & edge
Next edge

Print #fileNum, “}”
Close #fileNum
End Sub

3. シニアアーキテクトが解説する最適化の勘所

① `Application.ScreenUpdating = False` の絶対的必要性

VBAからMS Projectを操作する際、プロパティ(特に`Task.Predecessors`や`TextN`など)を読み書きするたびに、GUIのスレッドとCOMスレッドの間で同期が発生する。数千行のプロジェクトでこれを無防備に行うと、UIの再描画が走って処理が何十倍も遅くなる。必ず処理の最初に画面更新を止め、最後に復元させよ。

② VBScript.RegExp による堅牢なパース

`InStr`や`Mid`を使った自前パーサーは、プロジェクト管理者が「外部プロジェクトのリンク(`Path\to\file.mpp\12`)」や「独自のリソース制約」を入力した瞬間にバグる。正規表現を使うことで、ノイズとなるパス情報を削ぎ落とし、純粋なタスクIDと依存関係タイプ(`FS`, `SS`, `FF`, `SF`)およびラグを安全に抽出できる。

③ メモリのライフサイクル管理

VBAの`Collection`や`Object`は、スコープを抜けるまでメモリ上に残り続ける。特にCOMオブジェクト(`VBScript.RegExp`など)のループ内生成はメモリリークやパフォーマンス低下の温床となる。上記のコードでは、正規表現オブジェクトはループ外で1度だけ生成し、処理完了後は確実に解放(あるいはプロシージャ終了によるスコープアウト)させている。

4. システム間連携への応用:GraphvizからSVG/PNGへのコンパイル

抽出された `project_dependency.dot` ファイルは、そのままでは単なるテキストである。これをCI/CDパイプラインや社内サーバー上で自動的にビジュアル化するには、コマンドラインから Graphviz を呼び出すバッチ処理、あるいはVB.NET等の外部ラッパーを連携させる。

REM Windows環境におけるGraphvizの自動レンダリング例
dot -Tsvg project_dependency.dot -o project_dependency.svg

このパイプラインを構築することで、「Microsoft Projectのスケジュール変更をトリガーに、毎晩自動で最新のネットワーク図(PERT図)を社内ポータルに描画・公開する」というエンタープライズグレードのシステム連携基盤が完成する。

レガシーとモダンが交差する現場において、VBAは単なるマクロの域を超え、強力なETL(Extract, Transform, Load)ツールへと昇華する。この設計思想をあなたのプロジェクトにインストールせよ。

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