巨大なWBSを解体せよ:VBAによる「プロジェクト階層・依存関係」の動的抽出と外部参照の呪縛
大規模なProjectファイルは、それ自体が爆弾だ。
数千行のタスク、複雑に絡み合う前提条件、そして肥大化したメモリ消費。ある一点を超えた瞬間、MS Projectのパフォーマンスは崩壊し、ファイル破損の悪夢が始まる。
現場のシニアエンジニアなら知っているはずだ。巨大なプロジェクトを単一ファイルで管理しようとするのは、エンジニアリングの敗北であると。
今日は、Project VBAを駆使し、「特定階層を別ファイルへ切り出し、依存関係(Predecessors)を破壊せずに抽出する」という、極めて実戦的なアーキテクチャを解説する。
—
1. 依存関係(Dependency)の正体を暴く
MS Projectにおいて、タスク間の依存関係は `Task.TaskDependencies` コレクションで管理されている。これを単純に値としてコピーするだけでは不十分だ。
なぜなら、タスクID(Unique IDではない)は切り出し先で変わる可能性があるからだ。ここで重要なのは、「Unique ID」をキーとしたマッピングテーブルをメモリ上に構築し、参照先を動的に解決するというプロセスだ。
2. メモリ最適化とオブジェクトライフサイクルの管理
VBAで大規模なタスクコレクションをループさせると、GC(ガベージコレクション)が追いつかず、メモリリークやオートメーションエラーが頻発する。
- 明示的解放: `Set obj = Nothing` は儀式ではない。スコープを抜ける前に確実にメモリを解放せよ。
- イベント抑止: `Application.ScreenUpdating = False` だけでなく、`Application.Calculation = pjManual` に設定し、再計算プロセスを遮断せよ。
3. 実装:タスク抽出モジュールの核
以下に、対象タスク群を新規プロジェクトへコピーし、外部依存関係を再構築するコアロジックを示す。
‘ プロジェクト切り出し・依存関係保持モジュール
Option Explicit
Sub ExtractSubProject(sourceProj As Project, targetFolder As String, targetOutlineLevel As Integer)
Dim srcTask As Task
Dim newProj As Project
Dim taskMap As Object
Set taskMap = CreateObject(“Scripting.Dictionary”)
‘ パフォーマンス最適化
Application.Calculation = pjManual
‘ 新規プロジェクト生成
Set newProj = Application.Projects.Add(False)
‘ 1. タスクのコピーとUnique IDのマッピング
For Each srcTask In sourceProj.Tasks
If Not srcTask Is Nothing Then
If srcTask.OutlineLevel <= targetOutlineLevel Then
Dim newTask As Task
Set newTask = newProj.Tasks.Add(srcTask.Name)
' Unique IDをキーにして新旧のマッピングを保持
taskMap.Add srcTask.UniqueID, newTask.UniqueID
End If
End If
Next
' 2. 依存関係の再構築
Dim dep As TaskDependency
For Each srcTask In sourceProj.Tasks
If taskMap.Exists(srcTask.UniqueID) Then
For Each dep In srcTask.TaskDependencies
' 依存先も切り出し対象に含まれている場合のみリンクを張る
If taskMap.Exists(dep.From.UniqueID) Then
newProj.Tasks.UniqueID(taskMap(srcTask.UniqueID)).TaskDependencies.Add _
From:=newProj.Tasks.UniqueID(taskMap(dep.From.UniqueID)), _
Type:=dep.Type
End If
Next
End If
Next
' 3. 後始末
Application.Calculation = pjAutomatic
Set taskMap = Nothing
' ※保存処理は必要に応じてFileSystemObject等でパス生成
' newProj.SaveAs targetFolder & "\Extracted_WBS.mpp"
End Sub
---
4. チーフアーキテクトからの忠告
レガシー環境における「Windows API」の活用
もし、切り出したファイルをネットワークドライブ上の共有フォルダへ保存する場合、VBAのネイティブな `SaveAs` メソッドは同期処理ゆえに、I/O待ちでフリーズすることがある。
この場合、`kernel32.dll` の `CopyFileEx` を用いて非同期に近いI/O制御を行うか、あるいはファイルI/Oの完了を監視するシグナル処理を実装するのがプロの流儀だ。
外部参照リンクの罠
上記コードは「同一ファイル内」の依存関係を維持する設計だが、現実には「別のMPPファイル」を参照するケースも多い。その場合、`Task.ExternalTask` プロパティを走査し、`Project.Subprojects` コレクションを通じてリンクを再マッピングする必要がある。これは複雑なツリー走査になるため、再帰関数(Recursive Function)を用いて、スタックオーバーフローを回避しつつ深さ優先探索を行うこと。
結論:自動化の本質とは
自動化とは、単に「手作業を減らすこと」ではない。
「プロジェクトという巨大な生命体が持つ整合性を、システム的な強制力によって維持し続けること」だ。
WBSの分割は、単なるファイルの切り出しではなく、責任分界点の明確化そのものである。このコードをベースに、各社の固有環境に合わせて「エラーハンドリング(`On Error Resume Next` の乱用は厳禁)」を組み込み、堅牢なシステムを作り上げてもらいたい。
コードは嘘をつかない。論理的に組まれたプログラムは、必ず期待通りの挙動を返す。健闘を祈る。
