スケジューリング・エンジンの浄化:制約条件の全削除によるプロジェクト・リセットのアーキテクチャ
プロジェクト管理の現場において、MS Projectは単なる「ガントチャート作成ツール」ではない。それは、タスク間の依存関係(Predecessors/Successors)をノードとし、制約条件(Constraints)をエッジの重み付けとして扱う、極めて動的な有向非巡回グラフ(Directed Acyclic Graph: DAG)の計算エンジンである。
しかし、運用が長期化し、現場の人間が「とりあえずこのタスクをこの日に開始させたい」という安易な意志決定を繰り返すと、グラフの中に「Must Start On」や「Must Finish On」といった硬直的な制約条件(Hard Constraints)が大量に埋め込まれる。これらは計算エンジンの自由度を奪い、クリティカルパスの計算を歪め、プロジェクトの動的な再計算(Rescheduling)を不可能にする「毒」となる。
本稿では、複雑化したプロジェクトの計算モデルを一度完全に初期化し、すべてのタスクをプロジェクト開始日基準のASAP(As Soon As Possible)状態へ回帰させるための、極限まで最適化された実装手法を解説する。
1. 制約条件(Constraints)の本質的理解
MS Projectの計算エンジンは、以下の優先順位で日付を決定する。
1. 制約条件(Constraints): ユーザーが強制的に指定した日付。
2. 依存関係(Logic/Links): 先行タスクとの関係性。
3. リソース制約(Resource Leveling): リソースの空き状況。
「Must Start On」などの制約が付与された瞬間、そのタスクは「論理的な依存関係」よりも「ユーザーの指定」を優先する。これにより、先行タスクが遅延しても、そのタスクの開始日は動かない。これが積み重なると、プロジェクト全体のスケジュール・インテグリティ(整合性)は崩壊する。
我々エンジニアが目指すべきは、制約条件を `pjConstraintNone` にリセットし、純粋な論理関係のみに基づく計算モデルを再構築することである。
2. パフォーマンスとメモリ管理:大規模プロジェクトへの挑戦
数万タスク規模のプロジェクトを扱う場合、単純な `For Each` ループによる処理は致命的なパフォーマンス低下を招く。計算エンジンが各タスクの変更を検知するたびに再計算(Recalculation)を試みるからだ。
これを防ぐための鉄則は以下の3点である。
1. 計算モードの凍結: `Application.Calculation = pjManual` により、ループ中の再計算を完全に停止させる。
2. 画面更新の停止: `Application.ScreenUpdating = False` により、描画リソースを計算に集中させる。
3. COMオブジェクトのライフサイクル管理: 大規模なコレクションを走査する際、不要なオブジェクト参照を明示的に解放し、メモリリークを防止する。
3. 実装:Project ASAP Reset Engine
以下に、プロフェッショナルな現場でそのままデプロイ可能な、堅牢なリセット・モジュールを提示する。
Option Explicit
''' <summary>
''' プロジェクト内の全タスクから制約条件を削除し、完全なASAP状態へリセットする。
''' </summary>
''' <remarks>
''' このメソッドは計算エンジンの状態を一時的に「手動」に切り替え、
''' 計算負荷を最小限に抑えつつ、グラフの整合性を再構築する。
''' </remarks>
Public Sub ResetProjectToASAP()
Dim proj As MSProject.Project
Dim t As MSProject.Task
Dim taskCount As Long
Dim processedCount As Long
' エラーハンドリングの確立
On Error GoTo ErrorHandler
Set proj = ActiveProject
' 1. 計算エンジンの最適化(State Management)
' 変更のたびに再計算が走るのを防ぐため、計算モードを「手動」に固定する
Application.Calculation = pjManual
Application.ScreenUpdating = False
' 統計用
taskCount = proj.Tasks.Count
processedCount = 0
Debug.Print "--- Reset Process Started: " & Now & " ---"
Debug.Print "Total Tasks to process: " & taskCount
' 2. タスク・コレクションの走査
' 注意: For Each は COM オブジェクトの走査において標準的だが、
' 大規模プロジェクトでは、メモリ消費を抑えるためにオブジェクトの参照を意識する必要がある。
For Each t In proj.Tasks
' t が Nothing(削除済みタスクの残骸など)でないことを確認
If Not t Is Nothing Then
' 制約条件が 'None' でない場合のみ処理を行う(不要な書き込みを避ける)
If t.ConstraintType <> pjConstraintNone Then
' 制約条件を解除
t.ConstraintType = pjConstraintNone
' 注: 必要に応じて、強制的に入力された開始日/終了日もクリアする場合は
' t.Start = t.ProjectStart 等の処理を検討するが、
' 通常は ConstraintType の解除のみで論理的な ASAP 化は達成される。
processedCount = processedCount + 1
End If
End If
' 進捗をデバッグ出力(大規模プロジェクトでのウォッチ用)
If processedCount Mod 500 = 0 Then
Debug.Print "Processed: " & processedCount & " tasks..."
End If
Next t
' 3. 計算エンジンの再起動と整合性の確保
' 全ての制約が解除された後、一度だけ「再計算」を実行し、
' 依存関係に基づいた正しいスケジュールを確定させる。
Debug.Print "Constraints cleared. Re-calculating logic..."
Application.Calculate
' 4. 環境の復元
Application.Calculation = pjAutomatic
Application.ScreenUpdating = True
MsgBox "Project Reset Complete." & vbCrLf & _
"Tasks Processed: " & processedCount & " / " & taskCount, vbInformation, "Success"
CleanUp:
' オブジェクトの明示的な解放(メモリ管理の極意)
Set t = Nothing
Set proj = Nothing
Exit Sub
ErrorHandler:
' エラー発生時も、計算モードを自動に戻さない限り、ユーザーは操作不能に陥る。
' 必ず復元処理を通るように設計すること。
MsgBox "Critical Error during Reset: " & Err.Description, vbCritical, "Error"
Application.Calculation = pjAutomatic
Application.ScreenUpdating = True
Resume CleanUp
End Sub
4. アーキテクトの視点:レガシー環境とシステム間連携
このマクロを、単なる「ボタン付きツール」として終わらせてはならない。シニアエンジニアとして、以下の観点を持って運用に組み込むべきだ。
Windows API との境界
MS Project の VBA は COM(Component Object Model)ベースである。もし、この処理を外部の C# や VB.NET アプリケーションから制御する場合、`Project.Application` オブジェクトの参照を保持し続けると、プロセスがゾンビ化(残留)するリスクがある。外部連携を行う際は、`Marshal.ReleaseComObject` を用いて、COM 参照カウントを適切に管理することを忘れてはならない。
整合性の検証(Post-Condition Check)
リセット完了後、単に「終わりました」と表示するのではなく、「クリティカルパスに変更があったか」「プロジェクト終了日が予定より何日変動したか」をログとして出力する機能を付加せよ。これが、システム管理者としての「品質保証」である。
結論
制約条件の削除は、単なるデータの消去ではない。それは、プロジェクトという「動的な生命体」に対し、再び論理的な思考(計算)を許容するための「外科的手術」である。エンジニアは、その操作がプロジェクトの構造に与える影響を、数学的な確信を持って遂行しなければならない。
