「制約条件の地獄」から脱却せよ:サブプロジェクト統合時のスケジュール崩壊を防ぐ、極限の自動調整設計
プロジェクト管理の現場において、MS Projectの「サブプロジェクト統合」は、強力な武器であると同時に、スケジュールを破壊する時限爆弾でもある。
複数のプロジェクトを一つのマスタープロジェクトに統合した瞬間、ガントチャートが予期せぬ挙動を見せ、タスクが数ヶ月先に吹き飛んだり、逆にクリティカルパスが消失したりする。これはバグではない。「制約条件(Constraints)」と「スケジュール計算エンジン」の不整合が生んだ、設計上の必然的な帰結だ。
多くの開発者は、力技で「すべての制約をASAP(可能な限り早く)に書き換える」という安易なコードを書く。しかし、それはプロの仕事ではない。実務における制約には「意味」がある。それを無視した自動化は、プロジェクトの整合性を根底から破壊する。
本記事では、マスタープロジェクトへの統合時に発生する制約条件の競合を、ロジカルかつ安全に解消するための、プロダクションレベルの設計思想と実装手法を伝授する。
1. なぜスケジュールは「崩壊」するのか
MS Projectの計算エンジンは、以下の2つの要素の相互作用で動いている。
1. 論理関係(Dependencies): タスク間の「FS(終了後開始)」などのリンク。
2. 制約条件(Constraints): 「○月○日に開始しなければならない(Must Start On)」といった、論理関係を無視して強制的に日付を固定する命令。
サブプロジェクトを統合する際、サブプロジェクト側で設定されていた「Must Start On(強制開始日)」が、マスタープロジェクト側の論理関係(先行タスクの終了日など)と矛盾した場合、エンジンはどちらか一方を優先せざるを得ない。その結果、無理やり引き延ばされたタスクがドミノ倒しのようにスケジュールを狂わせるのだ。
我々が目指すべきは、「強制的な制約(Hard Constraint)」を「論理的な境界(Soft Constraint)」へと、文脈を維持したまま変換することである。
2. 設計思想:HardからSoftへの「昇格」
堅牢な自動調整ツールの設計には、以下の3ステップが必要となる。
Step 1: 制約の検知と分類
すべてのタスクを対象にするのではなく、`ConstraintType` を走査し、`pjTaskMustStartOn` や `pjTaskMustFinishOn` といった「Must系」の制約を持つタスクのみを抽出する。
Step 2: 制約の緩和(Softening)
「Must Start On(必ずこの日に開始)」を、そのまま「ASAP(制約なし)」に変えてはいけない。それは、本来守るべきデッドラインを放棄することを意味する。
代わりに、「Start No Earlier Than(これより前には開始できない)」へと変換する。これにより、「その日以降なら、論理関係に従って動ける」という柔軟性をエンジンに与えることができる。
Step 3: 計算エンジンの制御
タスクを一つ書き換えるたびにスケジュールを再計算させてはいけない。これはパフォーマンスの致命的な低下を招くだけでなく、不完全な状態での計算による二次的なエラーを引き起こす。
3. プロダクションコード:Constraint Resolver
以下に、実務でそのまま利用可能な、保守性と堅牢性を兼ね備えたVBAコードを示す。
Option Explicit
''' <summary>
''' サブプロジェクト統合時に発生する「強制制約(Must系)」を
''' 「開始可能制約(SNET/FNLT系)」へ安全に変換し、スケジュール崩壊を防ぐ。
''' </summary>
Public Sub ResolveConstraintConflicts()
Dim t As Task
Dim conflictCount As Long
Dim startTime As Double
' パフォーマンス最適化:計算モードをマニュアルに変更
' これを行わないと、タスク更新のたびに全工程が再計算され、処理が指数関数的に遅くなる
Application.Calculation = pjManual
On Error GoTo ErrorHandler
conflictCount = 0
startTime = Timer
Debug.Print "--- Constraint Resolution Started ---"
' 全タスクをループ(サブプロジェクト内のタスクも含む)
For Each t In ActiveProject.Tasks
' 空のタスク行をスキップ
If Not t Is Nothing Then
' 制約条件の判定
Select Case t.ConstraintType
' Case 1: Must Start On (強制開始日) -> Start No Earlier Than (開始可能日) へ変換
Case pjTaskMustStartOn
t.ConstraintType = pjStartNoEarlierThan
conflictCount = conflictCount + 1
Debug.Print "Converted [Must Start On] -> [SNET]: " & t.Name
' Case 2: Must Finish On (強制終了日) -> Finish No Later Than (終了可能日) へ変換
Case pjTaskMustFinishOn
t.ConstraintType = pjFinishNoLaterThan
conflictCount = conflictCount + 1
Debug.Print "Converted [Must Finish On] -> [FNLT]: " & t.Name
' Case 3: Must Start On (強制開始日) かつ 以前の制約
' 必要に応じて、その他のHard Constraintを追加していく
Case pjTaskStartOn
' Start On は「その日に開始」という強い制約。
' 統合時は SNET に落とすのが定石。
t.ConstraintType = pjStartNoEarlierThan
conflictCount = conflictCount + 1
End Select
End If
Next t
' 最後に一度だけ再計算を実行
Application.Calculation = pjAutomatic
MsgBox "処理完了" & vbCrLf & _
"調整したタスク数: " & conflictCount & vbCrLf & _
"実行時間: " & Format(Timer - startTime, "0.00") & " 秒", vbInformation, "Success"
Exit Sub
ErrorHandler:
' エラー発生時は必ず計算モードを自動に戻す(これを行わないと、ユーザーのMS Projectが壊れたように見える)
Application.Calculation = pjAutomatic
MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical, "Error"
End Sub
4. アーキテクトの視点:実務への実装における注意点
このスクリプトを単なる「コピペ」で終わらせてはいけない。システムとして組み込む際は、以下の3点を考慮せよ。
① 外部データベースとの同期(Single Source of Truth)
もし、制約条件(デッドライン)を外部のERPやデータベースで管理している場合、VBAで `ConstraintType` を書き換えた後に、「どの制約を緩和したのか」というログをDBに書き戻す設計が必要だ。
「いつ、どのタスクの制約を、どのロジックで緩和したか」が追跡できなければ、プロジェクト監査において致命的な欠陥となる。
② ユーザーへの「意図」の確認
完全に自動化するのではなく、統合直後に「以下のタスクの制約を緩和しました。よろしいですか?」というダイアログ、あるいは「緩和対象リスト」をExcel等に書き出して確認させるプロセスを挟むのが、現場で信頼されるツールの条件である。
③ オブジェクトのライフサイクルとメモリ管理
大規模プロジェクト(数万タスク規模)の場合、`For Each t In ActiveProject.Tasks` はメモリを大量に消費する。もしさらに高速化を求めるなら、`Task` オブジェクトを直接操作するのではなく、一旦配列に情報を格納するか、ActiveX経由でのAPI呼び出しを検討すべきだが、通常のプロジェクト規模であれば上記の `pjManual` による制御だけで十分なパフォーマンスが得られる。
結論
優れた自動化エンジニアは、ツールで「問題を消し去る」のではない。「問題を管理可能な状態に変換する」のだ。
「Must」を「No Earlier Than」に変える。この小さな、しかし本質的な設計思想の差が、プロジェクトの整合性を守り、エンジニアとしてのあなたの価値を決定づける。
Happy Coding. And keep your schedules logical.
