【テクニカル・上級編】WBSの階層構造を維持したまま特定タスク群を別ファイルへ切り出すモジュール開発 – Project VBA解析バイブル

スポンサーリンク

WBSの「断裂」を許さない:Microsoft Projectにおける階層構造の外部切り出し極意

大規模プロジェクトの管理において、単一の `.mpp` ファイルに数千のタスクを詰め込むことは、運用上の自殺行為に等しい。ファイルサイズの肥大化は描画の遅延を招き、計算エンジンのオーバーヘッドはプロジェクトマネージャーの思考を停止させる。

しかし、いざ「サブプロジェクト化」や「特定フェーズの切り出し」を試みようとすれば、MS Projectの気まぐれな仕様が牙を剥く。単純なコピペでは、苦労して構築したWBSの階層(Outline Level)は崩れ、タスク間の「依存関係(Links)」という血流は途絶える。

本稿では、Project VBAを掌握するアーキテクトの視点から、「階層構造を維持したまま、特定タスク群を依存関係ごと別ファイルへ外科手術のように切り出す」ための極限の自動化手法を解説する。

1. 階層構造の「論理的再構築」という難問

MS Projectにおいて、タスクの階層は `OutlineLevel` プロパティで管理されている。しかし、特定のタスク(例:特定のフラグが立ったタスク群)だけを抽出して別ファイルに書き出す際、その親タスク(Summary Task)を無視して書き出せば、抽出先ではすべてのタスクが「レベル1」にフラット化されてしまう。

真のプロフェッショナルが取るべきアプローチは、以下の3段階だ。

1. ターゲット・パスの特定: 抽出対象タスクからルート(最上位)に遡る親タスクの系譜をすべて特定する。
2. スタブ(Stub)の生成: 抽出先ファイルにおいて、階層を維持するためだけの「空の親タスク」を階層順に生成する。
3. 依存関係の再マッピング: 抽出元での `UniqueID` ではなく、抽出先での新しい `ID` に基づいて先行関係を再接続する。

2. パフォーマンスとメモリの最適化:APIとオブジェクト管理

MS ProjectはExcel以上にリソースを消費する。数千タスクを扱う場合、VBAの標準的なループ処理では、画面更新と再計算のオーバーヘッドで実行時間が指数関数的に増大する。

ここでは、`ScreenUpdating` の停止は当然として、`Calculation` モードの制御、そして何より COMオブジェクトの明示的解放 を徹底する。

‘ Windows APIによる高精度な実行時間計測(プロファイリング用)
Private Declare PtrSafe Function GetTickCount Lib “kernel32″ () As Long

”’

”’ プロジェクトの一部を抽出し、階層を維持したまま新プロジェクトを作成する
”’

Public Sub ExtractSubProjectWithHierarchy()
Dim srcProj As Project, destProj As Project
Dim t As Task, srcT As Task
Dim startTime As Long

Set srcProj = ActiveProject
startTime = GetTickCount()

‘ 1. アプリケーション状態の最適化
ToggleOptimizations False

‘ 2. 出力先プロジェクトの生成
Set destProj = Projects.Add(Visible:=False)

On Error GoTo ErrorHandler

‘ 3. 抽出ロジック:Flag1がTrueのタスクを対象とする
‘ 階層維持のため、対象タスクの親を辿りながらコピーを生成
Dim taskMap As Object
Set taskMap = CreateObject(“Scripting.Dictionary”) ‘ UniqueIDの対照表

For Each srcT In srcProj.Tasks
If Not srcT Is Nothing Then
‘ 特定条件(例:Flag1)またはその子孫であるか判定
If srcT.Flag1 Or IsDescendantOfSelected(srcT) Then
‘ 階層を維持しつつタスクを複製
CopyTaskWithHierarchy srcT, destProj, taskMap
End If
End If
Next srcT

‘ 4. 依存関係(Links)の再構築
‘ 抽出されたタスク群の中だけで完結するリンクを復元する
ReconstructDependencies srcProj, destProj, taskMap

‘ 5. 後処理
destProj.Title = “Extracted_WBS_” & Format(Now, “yyyymmdd_hhnnss”)
destProj.Application.Visible = True

MsgBox “抽出完了: ” & (GetTickCount() – startTime) & “ms”, vbInformation

CleanUp:
ToggleOptimizations True
‘ メモリ解放:Projectオブジェクトは明示的にNothingにしないとプロセスが残る
Set taskMap = Nothing
Set destProj = Nothing
Set srcProj = Nothing
Exit Sub

ErrorHandler:
MsgBox “Fatal Error: ” & Err.Description, vbCritical
Resume CleanUp
End Sub

3. 階層維持のコア・ロジック

タスクを単純にコピーするのではなく、「親が既に存在するか」をチェックしながら再帰的に構造を模倣するのが、アーキテクトの作法だ。

Private Sub CopyTaskWithHierarchy(srcT As Task, destProj As Project, ByRef taskMap As Object)
Dim destT As Task

‘ 既にコピー済みの場合はスキップ(親タスクの重複防止)
If taskMap.Exists(srcT.UniqueID) Then Exit Sub

‘ 親タスクが未作成なら、先に親を作成する(再帰)
If Not srcT.OutlineParent Is Nothing Then
If srcT.OutlineParent.Name <> srcProj.Name Then ‘ プロジェクト自身が親でない場合
CopyTaskWithHierarchy srcT.OutlineParent, destProj, taskMap
End If
End If

‘ タスクの作成
Set destT = destProj.Tasks.Add(srcT.Name)

‘ 属性の転記(必要な属性をここに並べる)
destT.OutlineLevel = srcT.OutlineLevel
destT.Duration = srcT.Duration
destT.Start = srcT.Start
destT.Finish = srcT.Finish
destT.Manual = srcT.Manual ‘ 手動スケジュール等の属性も維持

‘ UniqueIDのマップを記録
taskMap.Add srcT.UniqueID, destT.UniqueID
End Sub

4. 依存関係(Successors/Predecessors)の外科的結合

タスクを別ファイルに飛ばした際、最も壊れやすいのが「先行関係」だ。外部参照リンク(External Links)として残すべきか、それとも抽出ファイル内で完結させるべきか。

本コードでは、「抽出されたタスク同士のリンクのみを復元する」という、整合性の高いアプローチを提示する。

Private Sub ReconstructDependencies(srcProj As Project, destProj As Project, taskMap As Object)
Dim srcT As Task, destT As Task
Dim dep As TaskDependency
Dim srcPredecessorID As Long, destPredecessorUID As Long

For Each srcT In srcProj.Tasks
If Not srcT Is Nothing Then
‘ 抽出対象のタスクか確認
If taskMap.Exists(srcT.UniqueID) Then
Set destT = destProj.Tasks.UniqueID(taskMap(srcT.UniqueID))

‘ 先行タスクをループ
For Each dep In srcT.TaskDependencies
‘ 先行タスクが抽出対象に含まれている場合のみリンクを張る
If taskMap.Exists(dep.From.UniqueID) Then
destT.LinkPredecessors _
Tasks:=destProj.Tasks.UniqueID(taskMap(dep.From.UniqueID)), _
LinkType:=dep.Type, _
Lag:=dep.Lag
End If
Next dep
End If
End If
Next srcT
End Sub

Private Sub ToggleOptimizations(enable As Boolean)
With Application
.ScreenUpdating = enable
.Calculation = IIf(enable, pjAutomatic, pjManual)
‘ 大規模操作時のダイアログ抑止
.DisplayAlerts = enable
End With
End Sub

5. 伝説のチーフアーキテクトからの助言

このモジュールを運用する際、以下の「現場の真理」を忘れてはならない。

1. カレンダーの不一致: 抽出先プロジェクトの `Base Calendar` がデフォルト(24hやStandard)の場合、期間(Duration)は同じでも開始・終了日がズレる。必ず `Custom Calendars` も同時にエクスポートするロジックを組み込むか、テンプレートファイルをベースに生成せよ。
2. リソースの亡霊: リソースアサインメント(Assignments)を切り出す場合は、リソースシート自体のインポートが必要だ。リソース名が一致しないと、コスト計算がゼロになる。
3. `UniqueID` vs `ID`: 常に `UniqueID` をキーにせよ。`ID`(行番号)はタスクの挿入・削除で容易に変動する。

レガシーなシステムほど、こうした「構造の維持」に無頓着なコードが散見される。だが、我々シニアエンジニアの責務は、データのライフサイクルと整合性に責任を持つことだ。このコードは単なるツールではない。プロジェクトの「意志」を切り出し、別の器で再び鼓動させるための術式である。

この知見が、貴殿の統括するプロジェクトの健全な分割と、管理負荷の低減に寄与することを願う。

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