【テクニカル・上級編】【実務中級】リソース平準化と制約条件の競合をVBAで解決する優先順位調整 – Project VBA解析バイブル

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

制約と平準化の衝突を制御せよ:スケジューリング・エンジンの決定論的制御

プロジェクト管理における最大のパラドックスは、「人間が課した論理的制約(Constraints)」と「物理的なリソースの限界(Resource Capacity)」が衝突した瞬間に発生する。

MS Projectの平準化エンジンは強力だが、盲目的だ。リソースの重複を解消しようとするあまり、ユーザーが死守すべき「Must Start On(開始日指定)」などのハード制約を無視、あるいは無視せざるを得ない不整合なスケジュールを生成する。これをそのまま放置すれば、スケジュールは「絵に描いた餅」へと堕ちる。

本稿では、VBAを用いてこの「制約と平準化の競合」を、単なるエラーハンドリングではなく、アルゴリズムによる優先順位の再定義として解決する極限の知見を共有する。

1. スケジューリング・エンジンの内部力学

MS Projectにおける制約条件(`ConstraintType`)には、エンジンの挙動を決定づける階層が存在する。

1. Hard Constraints (`pjTaskMustStartOn`, `pjTaskMustFinishOn`)
エンジンの計算ロジックを強制的に上書きする。リソースが不足していても、この日付を動かすことはできない。結果として、リソースのオーバーアロケーション(過剰割り当て)が解消されず、平準化が失敗する。
2. Soft Constraints (`pjTaskStartNoEarlierThan`, `pjTaskFinishNoLaterThan`)
エンジンの計算ロジックと共存できる。平準化によって日付が後ろにずれることを許容する。

エンジニアが直面する問題は、「ビジネス上はHard(絶対)だが、平準化のプロセスにおいてはSoft(譲歩可能)として扱わなければ、平準化自体が成立しない」というジレンマだ。

我々が実装すべきは、「制約のSnapshot(スナップショット)取得 $\to$ 制約の緩和(Relaxation) $\to$ 平準化の実行 $\to$ 制約の再適用と乖離の評価」という、決定論的なパイプラインである。

2. 実装戦略:Constraint Relaxation Algorithm

以下のコードは、プロジェクト内のタスクを走査し、ハード制約を一時的にソフト制約へ変換して平準化を行い、その後、元の制約との乖離を判定・補正するアーキテクチャの核となる。

記憶領域の最適化:Class Moduleの活用

まず、制約の状態をメモリ上に保持するための専用クラスを定義する。`Dictionary`オブジェクトを乱用するのではなく、型定義されたクラスを用いることで、大規模プロジェクトにおけるメモリレイアウトの予測可能性を高める。

Class Module: `clsConstraintSnapshot`

' ==============================================================================
' Class: clsConstraintSnapshot
' Purpose: 制約条件のメタデータを保持し、メモリリークを防ぐための構造体
' ==============================================================================
Option Explicit

Public TaskID As Long
Public OriginalConstraintType As Long
Public OriginalDate As Variant ' Date型はNullを許容するためVariant
Public IsHardConstraint As Boolean

' コンストラクタ代わりの初期化メソッド
Public Sub Initialize(t As Task)
    Me.TaskID = t.ID
    Me.OriginalConstraintType = t.ConstraintType
    Me.OriginalDate = t.ConstraintDate
    
    ' Hard Constraint かどうかの判定(Must系を捕捉)
    Select Case t.ConstraintType
        Case pjTaskMustStartOn, pjTaskMustFinishOn
            Me.IsHardConstraint = True
        Case Else
            Me.IsHardConstraint = False
    End Select
End Sub

メインロジック:平準化制御エンジン

次に、メインの制御ロジックを記述する。ここでは、大量のタスクオブジェクトを扱う際のパフォーマンス低下を防ぐため、`Application.Calculation` の制御と、オブジェクトの明示的な解放(`Set obj = Nothing`)を徹底する。

Standard Module: `modScheduleEngine`

Option Explicit

' Windows API: プロセスの高精度タイマー(パフォーマンス計測用)

If VBA7 Then

Private Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long

Else

Private Declare Function GetTickCount Lib "kernel32" () As Long

End If

Public Sub ExecuteSmartLeveling() Dim t As Task Dim snapshotCol As New Collection Dim snap As clsConstraintSnapshot Dim startTime As Long On Error GoTo ErrorHandler ' 1. パフォーマンス計測開始 startTime = GetTickCount() ' 計算モードをマニュアルに設定(大規模プロジェクトでのUIフリーズ防止) ' ※MS ProjectではCalculationプロパティの直接制御は限定的なため ' DisplayAlertsを制御し、再計算のトリガーを最小限にする Application.DisplayAlerts = False Debug.Print "--- Phase 1: Constraint Snapshot ---" ' 2. 制約のスナップショット取得と緩和(Relaxation) ' 効率的なメモリ管理のため、Collectionにクラスインスタンスを格納 For Each t In ActiveProject.Tasks If Not t Is Nothing Then If t.ConstraintType = pjTaskMustStartOn Or t.ConstraintType = pjTaskMustFinishOn Then ' スナップショットを作成 Set snap = New clsConstraintSnapshot snap.Initialize t snapshotCol.Add snap ' 【核心】ハード制約をソフト制約(StartNoEarlierThan)へ一時的に緩和 ' これにより、平準化エンジンがリソースに基づき日付を動かせるようになる t.ConstraintType = pjTaskStartNoEarlierThan End If End If Next t Debug.Print "--- Phase 2: Resource Leveling ---" ' 3. リソース平準化の実行 ' 負荷の高い処理であるため、ここでエンジンに制御を戻す ' pjLevelingAllTasks を指定し、プロジェクト全体を最適化 Application.ResourceLeveling Debug.Print "--- Phase 3: Constraint Restoration & Conflict Resolution ---" ' 4. 制約の再適用と乖離の評価 ' 緩和した制約を元に戻すが、平準化によって日付が動いた場合は警告または調整を行う Dim i As Long For i = 1 To snapshotCol.Count Set snap = snapshotCol(i) Set t = ActiveProject.Tasks(snap.TaskID) If Not t Is Nothing Then ' 平準化後の日付が、元のハード制約の日付より後ろにずれているか判定 If t.Start > snap.OriginalDate Then ' 【戦略的判断】 ' ここで「制約を優先」するか「平準化を優先」するかをロジックで分ける ' 今回は「制約を優先」し、制約を元に戻すが、リソース競合は残る ' もしくは、ビジネスルールに基づき「新しい日付を新しい制約」として再定義する ' 例:制約を「Must」に戻すと、平準化の効果が消失するため、 ' 「新しい開始日」を「StartNoEarlierThan」として再設定するアプローチ t.ConstraintType = pjTaskStartNoEarlierThan t.ConstraintDate = t.Start ' 乖離が大きい場合はログ出力(実務ではセルへの書き込みやログファイル出力) Debug.Print "CONFLICT: Task " & t.ID & " moved from " & snap.OriginalDate & " to " & t.Start Else ' 乖離がない場合は元のハード制約に戻す t.ConstraintType = snap.OriginalConstraintType t.ConstraintDate = snap.OriginalDate End If End If ' メモリ解放(重要:大規模ループ内でのオブジェクト破棄) Set snap = Nothing Next i ' 後処理 Application.DisplayAlerts = True Debug.Print "Execution Complete. Elapsed: " & (GetTickCount() - startTime) & " ms" Exit Sub ErrorHandler: Application.DisplayAlerts = True MsgBox "Critical Error in Scheduling Engine: " & Err.Description, vbCritical End Sub

3. アーキテクトの視点:メモリとパフォーマンスの極限

シニアエンジニアとして、コードの背後にある「重み」を理解しておく必要がある。

オブジェクト・ライフサイクルとCOMのオーバーヘッド

MS Projectの `Task` オブジェクトは、COMインターフェースを通じてメモリ上に展開される。`For Each t In ActiveProject.Tasks` を実行する際、内部的には膨大な数のCOM参照カウンタが操作されている。
大規模プロジェクト(タスク数 $> 5,000$)では、`snapshotCol` に格納するオブジェクトの数を最小限に抑え、不要になった `snap` インスタンスは即座に `Set snap = Nothing` で解放することが、VBAのヒープメモリの断片化を防ぐ唯一の手段である。

Windows APIによるプロファイリング

コード内に `GetTickCount` を組み込んだのは、単なるデバッグのためではない。平準化アルゴリズムの実行時間を計測し、「制約緩和のロジックがスケジューリング・エンジンの計算量に対して、無視できるレベル($O(1)$ または $O(n)$)であること」を数学的に担保するためだ。もし制約の再適用ロジックが $O(n^2)$ になれば、プロジェクトの規模拡大とともにシステムは崩壊する。

4. 結論

リソース平準化と制約条件の競合は、単なる設定の問題ではなく、「ビジネス上の意志(Constraints)」と「物理的現実(Resources)」の交渉プロセスである。

VBAを用いてこのプロセスを自動化する際は、以下の3原則を忘れてはならない。
1. 制約を「緩和」してエンジンを走らせ、その後「再定義」せよ。
2. オブジェクトのライフサイクルを厳格に管理し、COMのオーバーヘッドを制御せよ。
3. 決定論的なロジックに基づき、平準化による「ズレ」を定量的に評価せよ。

これこそが、レガシーな環境においても、極めて堅牢なプロジェクト管理システムを構築するための、エンジニアリングの真髄である。

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