制約条件の「強制解除」によるスケジュール最適化:VBAを用いたボトルネック解消
こんにちは。開発プロジェクトの現場において、スケジュール管理とリソース最適化は常にエンジニアの頭を悩ませる難題だ。
特に、Project VBAを用いた工程管理の自動化において、ASAP(可能な限り早く)やALAP(可能な限り遅く)、あるいは「〇日以降に開始」といった硬直した制約条件(Constraints)が全体の工期を不当に引き延ばしているケースに何度も直面してきたはずだ。
「なぜこのタスクは、前工程がわずかに押しただけで全体のクリティカルパスをここまで歪めるのか?」
「どの制約が最大のボトルネックになっており、どこを緩めれば工期が最短化されるのか?」
今回は、Project VBAのオブジェクトモデルの深部をハックし、「制約条件の自動特定と一時的緩和による工期短縮シミュレーション」を実現する極限のプロダクションコードを伝授する。表面的なリファレンスをなぞるだけのコードとは一線を画す、実務の現場で耐えうる堅牢なアーキテクチャを解説しよう。
—
なぜ「愚直なスケジュール計算」は破綻するのか
多くのプログラマブルなスケジューラーは、MS Project等のエンジンが弾き出す制約日(ConstraintDate)をそのまま信仰し、タスクの移動を恐れる。しかし、プロジェクトマネジメントの現場における真の最適化とは、「どの制約がシステム上の足枷(ボトルネック)になっているかを定量的に暴き、戦略的に外すこと」に他ならない。
愚直にすべてのタスクをループさせ、手動で制約を変更しては再計算を走らせるアプローチは、VBAの実行速度を著しく低下させるだけでなく、プロジェクトのデータ整合性を破壊する。
堅牢な設計のための3大原則
1. オブジェクトのライフサイクルと計算エンジンの制御: 計算モード(`Calculation`)を適切に制御し、無駄な再計算イベントを排除する。
2. トランザクション的アプローチ: シミュレーション結果が不採用の場合に備え、変更前の状態を安全にロールバック(またはインメモリで比較)できる構造にする。
3. 影響度の定量化(SLA/Slack分析): 単に制約を外すのではなく、全トータルスラック(Total Slack)への寄与度が高いクリティカルな制約のみをピンポイントで特定する。
—
アーキテクチャ概要:ボトルネック自動解消エンジン
今回構築するVBAモジュールは、以下のステップで動作する。
1. ベースラインの工期取得: 現在のプロジェクト全体の完了日をスナップショットとして保持。
2. 制約タスクの特定: `ConstraintType` が ASAP/ALAP 以外(例: 「指定日開始(SNET)」や「指定日終了(SNLT)」など)であり、かつトータルスラックが負またはゼロに近いタスクを抽出。
3. 仮想緩和シミュレーション: 該当タスクの制約を一時的に「ASAP」に変更し、プロジェクト全体の完了日がどれだけ短縮されるかを計測。
4. ログ出力と最適化レポート: 最も工期短縮効果の高い制約を特定し、ユーザーに通知(または自動適用)。
—
プロダクションコード:制約最適化シミュレーター
以下のコードは、エラーハンドリング、パフォーマンスチューニング、そしてProjectのCOMオブジェクトの挙動を熟知した上で記述された実務直結のモジュールだ。
Option Explicit
‘ =====================================================================================
‘ Module:
‘ ModConstraintOptimizer
‘ Description:
‘ プロジェクト内の制約条件を解析し、工期を圧迫しているボトルネックを特定・緩和する
‘ 極限のスケジュール最適化エンジン
‘ =====================================================================================
Public Sub SimulateAndOptimizeConstraints()
Dim prj As Project
Set prj = ActiveProject
‘ パフォーマンス最大化のため、画面描画と自動計算を一時停止
Application.ScreenUpdating = False
Application.Calculation = pjCalculationManual
On Error GoTo ErrorHandler
Dim originalFinish As Date
originalFinish = prj.ProjectFinish
MsgBox “ベースライン工期完了日: ” & originalFinish & vbCrLf & _
“制約条件のボトルネック解析を開始します。”, vbInformation, “最適化エンジン起動”
Dim t As Task
Dim candidateCount As Long
candidateCount = 0
Dim targetTask As Task
Dim minFinish As Date
minFinish = originalFinish
‘ 最適化対象を保持するコレクション(簡易的にIDを保持)
Dim bestTaskID As Long
bestTaskID = 0
‘ Step 1: ボトルネック候補のタスクをスキャン
For Each t In prj.Tasks
If Not t Is Nothing Then
‘ サマリータスクではなく、かつマイルストーンでもない、硬い制約を持つタスクを対象とする
‘ 例: pjConstraintStartNoEarlierThan (SNET), pjConstraintMustStartOn (MSO) など
If Not t.Summary And t.ConstraintType <> pjConstraintAsSoonAsPossible And _
t.ConstraintType <> pjConstraintAsLateAsPossible Then
Dim originalConstraint As Long
Dim originalDate As Date
originalConstraint = t.ConstraintType
originalDate = t.ConstraintDate
‘ Step 2: 仮想的に制約を「ASAP」に緩和してシミュレーション
t.ConstraintType = pjConstraintAsSoonAsPossible
‘ 強制的にプロジェクト再計算を実行
prj.Calculate
‘ 工期短縮効果を検証
If prj.ProjectFinish < minFinish Then
minFinish = prj.ProjectFinish
bestTaskID = t.ID
End If
' 状態を即座に元に戻す(トランザクションの維持)
t.ConstraintType = originalConstraint
t.ConstraintDate = originalDate
prj.Calculate
End If
End If
Next t
' Step 3: 結果の出力と最適化の提案
Application.Calculation = pjCalculationAutomatic
Application.ScreenUpdating = True
If bestTaskID > 0 Then
Set targetTask = prj.Tasks(bestTaskID)
Dim daysSaved As Long
daysSaved = DateDiff(“d”, minFinish, originalFinish)
Dim msg As String
msg = “【ボトルネック特定完了】” & vbCrLf & _
“最大の工期遅延要因となっているタスクが見つかりました。” & vbCrLf & vbCrLf & _
“タスク名: ” & targetTask.Name & vbCrLf & _
“現在の制約: ” & GetConstraintName(targetTask.ConstraintType) & vbCrLf & _
“期待される工期短縮効果: ” & daysSaved & ” 日” & vbCrLf & vbCrLf & _
“この制約を自動的に解除(ASAP化)しますか?”
If MsgBox(msg, vbYesNo + vbQuestion, “最適化の確認”) = vbYes Then
‘ 正式に制約を解除
targetTask.ConstraintType = pjConstraintAsSoonAsPossible
prj.Calculate
MsgBox “制約の強制解除を適用しました。スケジュールが最適化されました。”, vbInformation, “完了”
Else
MsgBox “最適化はキャンセルされました。”, vbInformation, “キャンセル”
End If
Else
MsgBox “工期を短縮できる硬い制約条件は見つかりませんでした。”, vbInformation, “解析終了”
End If
Exit Sub
ErrorHandler:
‘ 異常終了時の安全策:アプリケーション状態を必ず復旧
Application.Calculation = pjCalculationAutomatic
Application.ScreenUpdating = True
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “致命的エラー”
End Sub
‘ =====================================================================================
‘ Helper Function: 制約タイプの数値から可読性の高い文字列を返す
‘ =====================================================================================
Private Function GetConstraintName(ByVal cType As Long) As String
Select Case cType
Case pjConstraintAsSoonAsPossible: GetConstraintName = “できる限り早く (ASAP)”
Case pjConstraintAsLateAsPossible: GetConstraintName = “できる限り遅く (ALAP)”
Case pjConstraintMustStartOn: GetConstraintName = “指定日開始 (MSO)”
Case pjConstraintMustFinishOn: GetConstraintName = “指定日終了 (MFO)”
Case pjConstraintStartNoEarlierThan: GetConstraintName = “これ以降に開始 (SNET)”
Case pjConstraintStartNoLaterThan: GetConstraintName = “これ以前に開始 (SNLT)”
Case pjConstraintFinishNoEarlierThan: GetConstraintName = “これ以降に終了 (FNET)”
Case pjConstraintFinishNoLaterThan: GetConstraintName = “これ以前に終了 (FNLT)”
Case Else: GetConstraintName = “不明な制約 (” & cType & “)”
End Select
End Function
—
コードの核心:なぜこの実装が堅牢なのか?
1. ScreenUpdating と Calculation の完全制御
Project VBAにおいて、タスクを1つ変更するたびにプロジェクト全体が再計算されると、処理が数分〜数十分単位でフリーズする原因になる。`Application.Calculation = pjCalculationManual` により計算を完全に手動化し、メモリ上で高速にシミュレーションを行った上で、最後に一括計算を走らせる設計にしている。
2. ステートレスなロールバック機構
シミュレーション中、一時的に制約を外して `prj.Calculate` を走らせた後、必ず元の状態に戻している。これにより、「シミュレーションを実行しただけで予期せずプロジェクトファイルが書き換わってしまった」という最悪のバグを完全に防いでいる。
3. 安全なエラーハンドリング(Fail-Safe)
万が一、ループの途中でエラーが発生した場合でも、`ErrorHandler` ラベルによって必ず画面描画と自動計算の設定が元の状態(Automatic)に復旧されるように担保している。実務ツールにおいて、途中で固まったまま落ちるマクロは「凶器」でしかない。
—
現場で使える実務上の注意点・データベース連携
- エンタープライズ環境(Project Server / Project Online)での注意点
ローカルファイルではなく、PWA(Project Web App)環境のサーバー側プロジェクトを対象にする場合、タスクの更新にはチェックアウト(CheckOut)処理や、エンタープライズリソースプールとの同期ラグを考慮する必要がある。サーバー連携型の場合は、トランザクションの粒度をさらに慎重に設計すること。
- 依存関係の循環参照(Circular References)
制約を外した瞬間に予期せぬリンク構造によって循環参照が発生する場合がある。Projectのエンジンはこれを検知してエラーを吐くため、事前に `Errors` コレクションの監視を入れるとさらに堅牢性が増す。
総括
スケジュール計算エンジンの制御とは、ツールの挙動を完全に手の内に収めることに他ならない。今回紹介した「制約条件の強制解除によるシミュレーション」は、単なるコードのテクニックではなく、プロジェクトの停滞をシステム的アプローチで打破するための強力な武器となる。
あなたの現場でも、このアーキテクチャを組み込み、無駄な制約に縛られたデスマーチからプロジェクトを解放してほしい。
