マスタープロジェクト統合における制約条件の「不整合」を制する:大規模スケジュール制御の極限設計
プロジェクトの規模が拡大し、複数のサブプロジェクトを一つのマスタープロジェクトへと統合するフェーズ。そこは、多くのエンジニアが「スケジュールが狂った」「計算が合わない」という泥沼に足を取られる戦場である。
MS Projectの計算エンジンは極めて強力だが、サブプロジェクト間で個別に設定された「制約条件(Constraints)」は、統合時に論理的な矛盾を引き起こす。`Must Start On`(指定開始日)や `Start No Earlier Than`(開始指定日)といった「硬い制約」が、サブプロジェクト間の依存関係(Predecessors)と衝突したとき、スケジュールは予測不能な挙動を見せる。
本稿では、単なるプロパティの書き換えではない。オブジェクトのライフサイクル、COM参照の管理、そして計算エンジンの挙動を完全に掌握した上での、「制約条件競合の自動解消アルゴリズム」の設計思想と実装について詳述する。
1. 根本的な問題:制約条件の「硬度」と計算エンジンの衝突
MS Projectにおける制約条件は、大きく分けて二つの性質を持つ。
1. Soft Constraints (ASAP/ALAP): スケジュール計算エンジンが依存関係に基づいて柔軟に日付を決定できる状態。
2. Hard Constraints (Must Start On, etc.): エンジンによる計算を強制的に上書き、あるいは論理矛盾を発生させる状態。
サブプロジェクトを統合する際、各サブプロジェクトの管理者が「とりあえず」設定した `Must Start On` が、マスタープロジェクトにおける先行タスクの遅延によって「論理的に不可能な日付」を要求することがある。このとき、エンジンの計算結果は、ユーザーの意図しない「スケジュール・ジャンプ」を引き起こす。
我々アーキテクトの任務は、統合時にこれらの「硬い制約」を検知し、プロジェクトの論理的整合性を維持しつつ、適切な「柔軟な制約」へと昇華(Reconcile)させることにある。
2. アーキテクチャ設計における三つの鉄則
大規模な統合スクリプトを組む際、初心者が犯す致命的なミスは「全タスクのループ内で、都度再計算を走らせる」ことだ。これは計算量を $O(n^2)$ 以上に跳ね上げ、メモリリークとアプリケーションのフリーズを招く。
I. 計算エンジンの制御(Calculation Mode Control)
統合処理中、`Application.Calculation` は必ず `pjCalculationManual` に設定しなければならない。すべての変更が完了し、制約条件の整合性が確保された後に、一度だけ `pjCalculationAutomatic` に戻す。これがパフォーマンスの決定的な差を生む。
II. COMオブジェクトの明示的破棄(Explicit Object Release)
VBAのガベージコレクションは、大規模なオブジェクト操作においては極めて不確実である。特にサブプロジェクトからマスターへとタスクが移行する際、メモリ上に残る「幽霊オブジェクト(Zombie Objects)」がメモリを圧迫する。ループ内での `Set Task = Nothing` は、単なる作法ではなく、システムの安定性を担保するための必須要件である。
III. Windows APIによる制御権の譲渡
大量のタスクを処理する場合、MS Projectは「応答なし」状態に陥りやすい。`Sleep` APIや `DoEvents` を戦略的に配置し、OSに対する制御権の譲渡を行うことで、システムのデッドロックを防ぐ。
3. 実装:制約条件自動解消エンジン
以下に、マスタープロジェクトに統合された全タスクを走査し、論理的矛盾を招きやすい「硬い制約」を検知して、ASAP(As Soon As Possible)へ自動変換するプロフェッショナル・グレードのコードを示す。
Option Explicit
' Windows APIの宣言:システムの安定性と、長時間実行時のフリーズ防止のため
If VBA7 Then
Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)
Else
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
End If
''' <summary>
''' マスタープロジェクト統合時に発生する制約条件の競合を解消する
''' </summary>
Public Sub ReconcileProjectConstraints()
Dim masterProj As Project
Dim t As Task
Dim conflictCount As Long
Dim startTime As Double
' 高精度なパフォーマンス計測用
startTime = Timer
Set masterProj = ActiveProject
' 1. 計算モードをマニュアルに設定(計算コストの最小化)
masterProj.Calculation = pjCalculationManual
On Error GoTo ErrorHandler
Debug.Print "--- Constraint Reconciliation Started ---"
' 2. タスクの走査
' 大規模プロジェクトでは、TaskItemsコレクションを直接回すよりも
' インデックス管理を行う方がメモリの断片化を防ぎやすい場合がある
For Each t In masterProj.Tasks
' 空のタスク(サマリータスクの隙間など)をスキップ
If Not t Is Nothing Then
' 3. 「硬い制約」の検知
' Must Start On (3), Must Finish On (4), Start No Earlier Than (2) 等を対象とする
' 統合時にこれらが残っていると、先行タスクとの間で論理的矛盾が生じる
If IsHardConstraint(t.ConstraintType) Then
' 競合解消:制約を ASAP (As Soon As Possible) に変更
' これにより、依存関係に基づいた自然なスケジュール計算が可能になる
t.ConstraintType = pjConstraintAsSoonAsPossible
conflictCount = conflictCount + 1
' ログ出力(実運用ではログファイルへの書き出しを推奨)
Debug.Print "Resolved: Task [" & t.Name & "] ID: " & t.ID & " | Type: " & t.ConstraintType
End If
' 4. メモリ最適化:ループ内でのオブジェクト参照の明示的解放
' 巨大なプロジェクトでは、これがメモリリーク防止の鍵となる
' (ただし、For Each の場合はエンジンが参照を保持するため、
' 大規模な場合はインデックスによるループと Set t = Nothing の組み合わせが最強)
End If
' 5. OSへの制御権譲渡(一定件数ごとに実行)
If conflictCount Mod 100 = 0 Then
DoEvents
Sleep 1 ' CPUの過負荷を軽減
End If
Next t
' 6. 計算エンジンの再始動
masterProj.Calculation = pjCalculationAutomatic
Debug.Print "--- Reconciliation Completed ---"
Debug.Print "Total Conflicts Resolved: " & conflictCount
Debug.Print "Elapsed Time: " & Round(Timer - startTime, 2) & " seconds"
MsgBox "制約条件の解消が完了しました。" & vbCrLf & _
"解消件数: " & conflictCount & " 件", vbInformation
Exit Sub
ErrorHandler:
' エラー発生時も、計算モードを自動に戻さないと、ユーザーが気づかずに作業を続けるリスクがある
masterProj.Calculation = pjCalculationAutomatic
MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
End Sub
''' <summary>
''' 制約条件が「硬い(Hard)」かどうかを判定する
''' </summary>
Private Function IsHardConstraint(ByVal cType As Long) As Boolean
Select Case cType
Case pjConstraintMustStartOn, _
pjConstraintMustFinishOn, _
pjConstraintStartNoEarlierThan, _
pjConstraintFinishNoLaterThan
IsHardConstraint = True
Case Else
IsHardConstraint = False
End Select
End Function
4. 深層解説:なぜこの設計なのか
`IsHardConstraint` の論理
なぜ `pjConstraintStartNoEarlierThan`(開始指定日)を対象に含めるのか。これは、統合されたプロジェクトにおいて、先行タスクが完了していないにもかかわらず、この制約が残っていると、タスクが「先行タスクを無視して、指定日より前には動けない」という、不自然な「浮き(Float)」を生じさせるからだ。これらを `pjConstraintAsSoonAsPossible` に変換することで、プロジェクト全体のクリティカルパスが正しく再計算される。
メモリ管理の極意
コード内で `conflictCount Mod 100` を用いて `DoEvents` を呼び出している点は、実務において極めて重要だ。数万タスク規模のプロジェクトでは、VBAの実行スレッドが長時間占有されると、Windowsのウィンドウマネージャーが「応答なし」と判断し、プロセスを強制終了させるリスクがある。これを防ぐには、定期的にOSに「息をつかせる」設計が必要だ。
実務におけるデプロイメント
このスクリプトを社内システムとして提供する場合、単一のモジュールとしてではなく、「統合前チェック・統合後修正」というパイプラインの一部として組み込むべきである。
1. Pre-check: 各サブプロジェクトの制約条件の分布をレポート。
2. Integrate: マスターへの統合。
3. Reconcile: 本スクリプトによる制約解消。
4. Post-check: スケジュール変動の差異分析。
結言
Project VBAにおける自動化は、単に「操作を楽にする」ことではない。大規模な複雑系において、「決定論的な挙動を保証する」ことこそが、真のエンジニアリングである。
制約条件の競合を制御下に置くことは、プロジェクトのタイムラインという「不確実なもの」を、エンジニアリングの「制御可能な変数」へと変えるプロセスに他ならない。この知見を、貴殿のシステム設計の礎としていただきたい。
