Project VBAの深淵:Excelマッピングによる依存関係自動構築の「禁じ手」と最適解
プロジェクト管理の現場において、MS ProjectのVBA操作は「鬼門」と呼ばれる。特に、Excelで管理された膨大なWBSをProjectへ流し込み、先行タスク(Predecessors)の依存関係を構築する際、多くのエンジニアが「IDの不一致」という泥沼に沈む。
ID(連番)は行挿入や並び替えで容易に崩壊する。貴殿が向き合うべきは、常に`UniqueID`という不変のアイデンティティである。今回は、この「動的マッピング」をシステムアーキテクチャの視点から紐解く。
—
1. なぜ「行番号」ではなく「UniqueID」なのか
MS Projectのオブジェクトモデルにおいて、`ID`プロパティはUI上の行番号に過ぎない。しかし、`UniqueID`はタスクが生成された瞬間に割り振られる不変の識別子だ。
Excel上の「先行タスク番号」をProjectのIDと直結させる愚を犯してはならない。必ず「Excel上のキー」と「ProjectのUniqueID」を紐付ける辞書(Dictionaryオブジェクト)をメモリ上に展開せよ。
2. 検索ロジックの極限:O(n)の計算量を維持せよ
数千行のタスクに対し、ループ内で`Find`メソッドや`Tasks.Item`を繰り返すのは、パフォーマンスの自殺行為だ。Projectのタスクコレクションを全探索するのではなく、一度だけ走査して`Scripting.Dictionary`に格納する。
‘ 依存関係構築の最適化:Dictionaryを用いたUniqueID高速マッピング
Public Sub LinkTasksFromExcel(targetProject As MSProject.Project)
Dim taskDict As Object
Set taskDict = CreateObject(“Scripting.Dictionary”)
‘ 1. タスクのUniqueIDとオブジェクト参照をメモリにマッピング
‘ これにより、後の検索コストをO(1)に収束させる
Dim t As MSProject.Task
For Each t In targetProject.Tasks
If Not t Is Nothing Then
taskDict.Add CStr(t.UniqueID), t
End If
Next t
‘ 2. Excel上の先行タスク情報を読み込み、リンクを構築
‘ ここではExcelシート”Sheet1″のA列:先行タスクID, B列:後続タスクIDと仮定
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets(“Sheet1”)
Dim lastRow As Long: lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
Dim i As Long
For i = 2 To lastRow
Dim predID As String: predID = ws.Cells(i, 1).Value
Dim succID As String: succID = ws.Cells(i, 2).Value
If taskDict.Exists(predID) And taskDict.Exists(succID) Then
‘ リンク構築(FS:終了-開始関係)
taskDict(succID).TaskDependencies.Add taskDict(predID), pjFinishToStart
End If
Next i
‘ 3. メモリの明示的解放(VBAにおける生存戦略)
Set taskDict = Nothing
End Sub
3. レガシー環境とWindows APIの静かなる協調
大規模なProject VBAでは、COMサーバーの応答待ちでプロセスがフリーズする事態が頻発する。特にGUI更新を伴うとパフォーマンスは劇的に低下する。
`Application.ScreenUpdating = False` は基本だが、さらに踏み込んで`DoEvents`の制御を徹底せよ。また、極めて巨大なスケジュールを扱う場合、Windows APIの`Sleep`関数を用いて、CPU負荷を調整しつつプロセスの占有時間を制御するのが、真のシニアエンジニアの流儀である。
If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
Private Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
End If
‘ 大規模処理の合間に挟むことで、Projectの描画エンジンとの競合を回避する
Private Sub ThrottleProcess()
DoEvents
Sleep 10 ‘ 10msの小休止が、COMのスタックオーバーフローを防ぐ
End Sub
4. アーキテクトからの提言:例外処理の哲学
システム間連携における最大の敵は「データ整合性の欠如」である。
`taskDict.Exists`による存在チェックは必須だが、それ以上に「先行タスクが存在しない場合」や「循環参照が発生する場合」をどうハンドリングするかが、システムの堅牢性を決める。
- 循環参照の検知: Projectの`Task.TaskDependencies`は、不正なリンクを張ろうとすると例外を投げる。このエラーを捕捉し、ログファイル(テキストストリーム推奨)へ「どのUniqueIDとどのUniqueIDの間でエラーが起きたか」を書き出すロジックを必ず組み込め。
- オブジェクトの解放: `Set t = Nothing` をループ内で行うのはオーバーヘッドだが、サブルーチン終了時には必ず参照をクリアする。メモリリークは、数日間稼働し続ける長時間プロセスにおいて確実にシステムを殺す。
結論
プロジェクトの依存関係構築は、単なるデータの転記ではない。それは、無秩序なExcelの海から、Projectという論理空間へ秩序を投影する儀式である。
UniqueIDによるマッピング、Dictionaryによる高速化、そしてAPIによるプロセス制御。これらを掌握したとき、貴殿のVBAは「場当たり的なマクロ」から「堅牢なシステム」へと昇華する。
コードは嘘をつかない。だが、書いたエンジニアの思想はコードに宿る。次世代に保守を委ねる覚悟で、潔いロジックを書き上げよ。
