【実務・中級編】【実務中級】制約条件の変更がクリティカルパスに与える影響をVBAで事前シミュレーション – Project VBA解析バイブル

スポンサーリンク
スポンサーリンク

破壊的変更を避け、未来を予測せよ:MS Project VBAにおける「制約条件シミュレーション」の極意

スケジュール管理における「制約条件(Constraint)」の変更は、単なる日付の書き換えではない。それは、プロジェクトの時間的トポロジー(構造)を根本から作り変える、極めてリスクの高い外科手術である。

多くの開発者が陥る致命的なミスは、現在稼働中のプロジェクト(ActiveProject)に対して、直接 `Task.ConstraintType` を書き換えてしまうことだ。一度計算エンジンが走り、クリティカルパスが再計算された後で「やっぱり元に戻したい」と思っても、計算の連鎖(Ripple Effect)によって、どのタスクがどの制約に起因して動いたのかを完全に追跡することは不可能に近い。

本記事では、プロジェクトの整合性を破壊することなく、制約変更がクリティカルパスに与える影響を事前に算出する「サンドボックス・シミュレーション・パターン」の設計と実装について、アーキテクトの視点から詳説する。

1. 設計思想:破壊的変更を排除する「サンドボックス・パターン」

プロフェッショナルな自動化ツールにおいて、最も重要な原則は「データの不可逆的な変更を避ける」ことだ。

シミュレーションを行う際、我々が取るべき戦略は以下のステップに集約される。

1. Snapshot (スナップショット): 現在のプロジェクトの状態を、メモリ上または一時ファイルとして完全に複製する。
2. Sandbox (砂場): 複製したプロジェクト(Sandbox)に対してのみ、制約条件(ASAP/ALAP/Start No Earlier Than等)を適用する。
3. Analyze (分析): Sandbox内でのクリティカルパス、総余裕(Total Slack)、終了日を抽出する。
4. Compare (比較): オリジナルのデータとシミュレーション結果を突き合わせ、差分をレポートする。
5. Dispose (破棄): 使用したSandboxを即座に破棄し、メモリを解放する。

この設計により、万が一計算エンジンが予期せぬ挙動を示しても、メインのプロジェクトデータが汚染されることは絶対にない。

2. 実装:堅牢なシミュレーション・エンジンの構築

単なるプロシージャ(Sub)ではなく、クラスモジュールを用いたオブジェクト指向設計を推奨する。これにより、シミュレーションの状態管理と、エラー発生時のクリーンアップが容易になる。

クラスモジュール: `clsScheduleSimulator`

このクラスは、シミュレーションのライフサイクルを管理する。

' Class Module: clsScheduleSimulator
Option Explicit

Private pOriginalProject As MSProject.Project
Private pSandboxProject As MSProject.Project
Private pTargetTaskID As Long
Private pNewConstraint As MSProject.PjConstraintType

' シミュレーション結果を格納する構造体(簡易版)
Public Type SimulationResult
    TaskName As String
    OriginalFinish As Date
    SimulatedFinish As Date
    IsCriticalChanged As Boolean
    NewTotalSlack As Double
End Type

' --- 初期化 ---
Public Sub Initialize(ByRef targetProject As MSProject.Project, ByVal taskID As Long)
    Set pOriginalProject = targetProject
    pTargetTaskID = taskID
End Sub

' --- シミュレーション実行 ---
Public Function RunSimulation(ByVal constraint As MSProject.PjConstraintType) As SimulationResult
    Dim res As SimulationResult
    Dim simTask As MSProject.Task
    
    On Error GoTo ErrorHandler
    
    ' 1. Sandboxの作成 (Copyメソッドを使用して完全な複製を作成)
    ' Note: 大規模プロジェクトの場合、ファイル保存を経由したほうがメモリが安定するが、
    ' 高速化のためここではメモリ上のコピーを試みる。
    pOriginalProject.Copy
    Set pSandboxProject = Application.ActiveProject ' Copy直後はActiveProjectになる
    
    ' 2. 制約条件の適用
    Set simTask = pSandboxProject.Tasks(pTargetTaskID)
    simTask.ConstraintType = constraint
    
    ' 3. 再計算の強制
    pSandboxProject.Calculate
    
    ' 4. 結果の抽出
    ' オリジナルのタスク情報を取得(比較用)
    Dim origTask As MSProject.Task
    Set origTask = pOriginalProject.Tasks(pTargetTaskID)
    
    res.TaskName = origTask.Name
    res.OriginalFinish = origTask.Finish
    res.SimulatedFinish = simTask.Finish
    res.NewTotalSlack = simTask.TotalSlack
    
    ' クリティカルパスの変化判定 (Slackが0に近いかどうか)
    ' 浮動小数点の誤差を考慮し、極小値を閾値とする
    res.IsCriticalChanged = (Abs(simTask.TotalSlack) < 0.01) <> (Abs(origTask.TotalSlack) < 0.01)
    
    RunSimulation = res
    GoTo CleanUp

ErrorHandler:
    MsgBox "Simulation Error: " & Err.Description, vbCritical
    RunSimulation = TypeOf Empty ' エラー時は空の構造体を返す
    
CleanUp:
    ' 5. Sandboxの破棄 (非常に重要: メモリリークを防ぐ)
    If Not pSandboxProject Is Nothing Then
        pSandboxProject.Close SaveChanges:=pjDoNotSave
        Set pSandboxProject = Nothing
    End If
End Function

標準モジュール: 実行制御ロジック

メインロジックでは、ユーザーの入力を受け取り、結果をExcelやレポートに書き出す処理を行う。

' Standard Module
Option Explicit

Sub ExecuteConstraintImpactAnalysis()
    Dim sim As clsScheduleSimulator
    Dim result As clsScheduleSimulator.SimulationResult
    Dim targetTaskID As Long
    
    ' 例として、選択中のタスクを対象とする
    If Application.Selection.Count = 0 Then
        MsgBox "タスクを選択してください。"
        Exit Sub
    End If
    
    targetTaskID = Application.Selection(1).Task.ID
    
    ' シミュレーターのインスタンス化
    Set sim = New clsScheduleSimulator
    sim.Initialize ActiveProject, targetTaskID
    
    ' シミュレーション実行: 制約を "Must Finish On" (pjMustFinishOn) に変更した場合
    ' ※実際の業務ではユーザーに選択させる
    Debug.Print "--- Simulation Start ---"
    result = sim.RunSimulation pjMustFinishOn
    
    ' 結果の出力
    If result.TaskName <> "" Then
        Debug.Print "Task: " & result.TaskName
        Debug.Print "Original Finish: " & result.OriginalFinish
        Debug.Print "Simulated Finish: " & result.SimulatedFinish
        Debug.Print "Critical Path Changed: " & IIf(result.IsCriticalChanged, "YES", "NO")
        Debug.Print "New Slack: " & result.NewTotalSlack
        
        MsgBox "シミュレーション完了。" & vbCrLf & _
               "変更後の終了日: " & result.SimulatedFinish & vbCrLf & _
               "クリティカルパスの変化: " & IIf(result.IsCriticalChanged, "あり", "なし"), vbInformation
    Else
        MsgBox "シミュレーションに失敗しました。", vbExclamation
    End If
    
    Set sim = Nothing
End Sub

3. プロフェッショナルのための技術的深掘り

COMオブジェクトのライフサイクル管理

MS ProjectのVBA操作において、最も多いバグの原因は「ゾンビプロセス」だ。`pSandboxProject.Close` を呼び出さずにオブジェクトの参照を外すだけでは、バックグラウンドで `MSProject.exe` がメモリを食いつぶし続ける。必ず `Close` メソッドを明示的に呼び出し、その後に `Set obj = Nothing` を行う。これが「堅牢なコード」の最低条件である。

計算エンジンの「重み」を理解する

`Project.Calculate` は非常に重い処理である。数千タスク規模のプロジェクトで、ループの中で何度も `Calculate` を呼び出すような実装は、パフォーマンスの観点から「論外」だ。
もし複数の制約を連続してテストしたい場合は、以下の設計を検討せよ。

  • Batch Simulation: 複数の制約パターンを一つのSandboxに対して順次適用し、結果を配列に溜めてから一括で出力する。

データベース・外部連携時の注意点

このシミュレーション結果をExcelやSQL Serverに書き出す場合、「どのバージョンの、どの時点のデータに対するシミュレーションか」というメタデータを必ずセットで保存せよ。
制約条件の変更は「仮説」に過ぎない。後日、「あの時のシミュレーションと実際の進捗がなぜズレたのか」を検証するためには、シミュレーション時のプロジェクトのタイムスタンプと、適用した制約のパラメータをログとして残すことが、真のアーキテクトの仕事である。

まとめ

制約条件の変更をシミュレーションするツールを作成する際、我々が目指すべきは単なる「計算の自動化」ではない。「プロジェクトの意思決定を、安全かつ科学的に支援する基盤」の構築である。

サンドボックス・パターンを採用し、オブジェクトのライフサイクルを厳格に管理することで、あなたは「壊れやすいツール」を作る開発者から、「信頼されるシステム」を設計するエンジニアへと昇華することができる。

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