【テクニカル・上級編】【プロ向け】計算エンジンが「矛盾」を検知した際のVBAハンドリング:Project.Task.ConstraintDateの検証アルゴリズム – Project VBA解析バイブル

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

スケジュール計算エンジンの論理的破綻を解体する:ConstraintDate検証アルゴリズムの設計思想

大規模なプロジェクト・スケジューリングにおいて、Microsoft Projectの計算エンジンは極めて強力な数学的モデルに基づいている。しかし、その強力さゆえに、ユーザーが入力した「制約条件(Constraints)」と「依存関係(Dependencies)」が衝突した際、エンジンは静かに、しかし決定的な「論理的矛盾」を引き起こす。

「Must Start On(指定開始日)」を設定したタスクが、先行タスクの完了遅延によって物理的にその日に開始できない場合、エンジンは警告を出すが、自動計算が走る中でその矛盾は複雑なカスケード(連鎖)を引き起こし、プロジェクト全体のクリティカルパスを不透明にする。

本稿では、単なるエラーハンドリングの域を超え、計算エンジンが「矛盾」を検知した際に、どのオブジェクトが、どのような論理的制約によって、どのようにスケジュールを歪めているかを特定するための、プロフェッショナル向けの検証アルゴリズムとその実装アーキテクチャについて詳述する。

1. 制約条件の矛盾:論理的コンフリクトの正体

MS Projectにおける矛盾は、大きく分けて二つのレイヤーで発生する。

1. ハード・コンストレイント(Hard Constraints): `pjTaskMustStartOn` や `pjTaskMustFinishOn` など、エンジンの計算ロジックを強制的に上書きする制約。
2. ソフト・コンストレイント(Soft Constraints): `pjTaskStartNoEarlierThan` など、依存関係との妥協点を探る制約。

矛盾が発生するメカニズムは、「制約日付(ConstraintDate)」と「計算された日付(Calculated Date)」の乖離である。エンジンの内部計算において、依存関係による計算結果(Successor logic)が制約条件(Constraint logic)を破ろうとしたとき、プロジェクトの整合性は崩壊する。

シニアエンジニアが構築すべきツールは、この「エンジンの妥協点」を炙り出し、設計上の意図(Intent)と実行可能なスケジュール(Feasibility)の乖離を可視化することにある。

2. 高パフォーマンスな検証アーキテクチャ

数万タスク規模のプロジェクトにおいて、単純な `For Each task In ActiveProject.Tasks` ループを回すのは、素人の仕事だ。COMオブジェクトへのアクセスはオーバーヘッドが極めて大きく、不適切なループはメモリの断片化と、実行時間の指数関数的な増大を招く。

最適化の指針

  • オブジェクト・ライフサイクルの厳格な管理: ループ内でのオブジェクト参照は、スコープを最小限に抑え、明示的に `Nothing` を代入して参照カウントを減らす。
  • 計算エンジンの状態制御: 検証を行う前に `Application.Calculation = pjManual` を検討し、検証プロセス中の再計算による計算負荷の暴走を防ぐ。
  • 早期リターン(Early Exit)戦略: 制約タイプが `pjTaskAsSoonAsPossible`(ASAP)などの制約を持たない場合、検証ロジックをスキップし、CPUサイクルを節約する。

3. 実装:ConstraintDate 検証アルゴリズム

以下に、制約条件の矛盾を検知し、その原因となるタスクの属性を抽出する堅牢なコード・プロトタイプを示す。これは単なるデバッグ用ではなく、大規模システムにおける「整合性監査エンジン」のコアロジックとして設計している。

Option Explicit

' ======================================================================================
' Module: SchedulerIntegrityAuditor
' Description: MS Projectの計算エンジンにおける制約矛盾を特定する高度な監査エンジン
' Author: Legendary Chief Architect
' ======================================================================================

' 矛盾の深刻度を定義する列挙型
Public Enum ConflictSeverity
    sevLow = 0        ' 警告レベル(ソフト制約の乖離)
    sevMedium = 1     ' 注意レベル(スケジュールへの影響あり)
    sevHigh = 2       ' 致命的レベル(ハード制約の違反)
End Enum

' 監査結果を格納する構造体
Public Type ConflictReport
    TaskID As Long
    TaskName As String
    ConstraintType As Long
    ConstraintDate As Date
    CalculatedDate As Date
    Severity As ConflictSeverity
    Message As String
End Type

''' <summary>
''' プロジェクト全体の制約矛盾をスキャンし、レポートを生成する
''' </summary>
Public Function AuditProjectConstraints(ByRef targetProject As MSProject.Project) As Collection
    Dim reports As New Collection
    Dim t As MSProject.Task
    Dim report As ConflictReport
    
    ' 計算エンジンの負荷軽減のため、一時的に手動計算に設定することを推奨
    ' ただし、検証自体には最新の計算結果が必要なため、直前に一回実行する
    targetProject.Calculate
    
    On Error GoTo ErrorHandler

    ' タスク集合のイテレーション
    ' 大規模プロジェクトでは、TaskIDによるアクセスの方が安定する場合がある
    For Each t In targetProject.Tasks
        ' Nullタスク(削除済みタスクの残骸)のスキップ
        If Not t Is Nothing Then
            
            ' 制約条件を持つタスクのみを対象とする(パフォーマンス最適化)
            If t.ConstraintType <> pjTaskAsSoonAsPossible Then
                
                ' アルゴリズムの核:制約日付と計算結果の比較
                If CheckConstraintViolation(t, report) Then
                    reports.Add report
                End If
                
            End If
            
            ' COMオブジェクトの参照カウントを適切に管理するための明示的解放
            ' (ループ内でのメモリリーク防止)
            ' Set t = Nothing ' For Each の場合は自動管理されるが、複雑な参照時は意識すること
        End If
    Next t

CleanExit:
    Set AuditProjectConstraints = reports
    Exit Function

ErrorHandler:
    ' 予期せぬエラー(オブジェクトの消失等)をトラップ
    Debug.Print "Critical Error in Audit Loop: " & Err.Description
    Resume CleanExit
End Function

''' <summary>
''' 個別のタスクに対して制約矛盾のロジックを適用する
''' </summary>
Private Function CheckConstraintViolation(ByRef t As MSProject.Task, ByRef report As ConflictReport) As Boolean
    CheckConstraintViolation = False
    
    Dim cDate As Date
    Dim calcDate As Date
    
    ' 制約日付の取得
    cDate = t.ConstraintDate
    
    ' 制約タイプに応じた検証ロジックの分岐
    Select Case t.ConstraintType
        
        Case pjTaskMustStartOn, pjTaskMustFinishOn
            ' ハード制約:計算された日付が制約日付から外れている場合、
            ' エンジンは強制的に日付を動かそうとするが、依存関係との衝突が起きている。
            calcDate = IIf(t.ConstraintType = pjTaskMustStartOn, t.Start, t.Finish)
            
            ' 浮動小数点誤差や時刻の微細な差を考慮し、許容範囲(1分以内)で比較
            If Abs(CDate(calcDate) - CDate(cDate)) > TimeValue("00:01:00") Then
                report.TaskID = t.ID
                report.TaskName = t.Name
                report.ConstraintType = t.ConstraintType
                report.ConstraintDate = cDate
                report.CalculatedDate = calcDate
                report.Severity = sevHigh
                report.Message = "Hard Constraint Violation: Calculated date diverges from Must-date."
                CheckConstraintViolation = True
            End If
            
        Case pjTaskStartNoEarlierThan, pjTaskFinishNoLaterThan
            ' ソフト制約:依存関係によって制約日付を越えてしまった場合
            calcDate = IIf(t.ConstraintType = pjTaskStartNoEarlierThan, t.Start, t.Finish)
            
            ' 制約日付よりも「前」に計算されてしまった場合(理論上ありえないが、ロジックの不整合を検知)
            If (t.ConstraintType = pjTaskStartNoEarlierThan And calcDate < cDate) Or _
               (t.ConstraintType = pjTaskFinishNoLaterThan And calcDate > cDate) Then
                
                report.TaskID = t.ID
                report.TaskName = t.Name
                report.ConstraintType = t.ConstraintType
                report.ConstraintDate = cDate
                report.CalculatedDate = calcDate
                report.Severity = sevMedium
                report.Message = "Soft Constraint Conflict: Dependency forced a date violation."
                CheckConstraintViolation = True
            End If
            
    End Select
End Function

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

上記のコードにおいて、シニアエンジニアが注目すべきは単なるロジックではない。「オブジェクトの振る舞い」だ。

1. COM Interop のコスト

`t.ConstraintType` や `t.Start` を呼び出すたびに、VBA(クライアントプロセス)とMS Project(サーバープロセス)の間でコンテキストスイッチが発生する。これを最小化するために、もし可能であれば、必要なプロパティを一度配列や構造体にバッファリングする設計が望ましい。しかし、MS Projectのオブジェクトモデルは巨大であるため、全プロパティをコピーするよりも、「矛盾の疑いがあるタスク」に絞ってから詳細プロパティにアクセスする「二段階スキャン方式」が最も効率的である。

2. Windows API によるメモリ監視(補足的知見)

もしこのツールを、数千のプロジェクトファイルを連続して処理するバッチ処理(サーバーサイド自動化)として運用する場合、VBAのメモリ管理だけでは不十分だ。MS Projectのプロセス(`WINPROJ.EXE`)が、COM参照の解放漏れによってメモリを食いつぶしていく「メモリリーク現象」は、レガシーな自動化環境では日常茶飯事である。
そのような場合、`GetProcessMemoryInfo` などのWindows APIを呼び出し、各プロジェクト処理の終了後にプロセスのワーキングセットが正常に縮小しているかを監視する仕組みを組み込むべきである。

5. 結論

スケジュール計算エンジンの矛盾を検知するとは、単にエラーを捕まえることではない。それは、「ユーザーの意図」と「数学的現実」の乖離を特定することである。

今回提示した `ConstraintDate` 検証アルゴリズムは、計算エンジンのブラックボックスの中に潜む論理的矛盾を、構造化されたデータとして抽出するための第一歩となる。この知見を基に、ダッシュボードへの可視化や、矛盾を自動修正するリカバリエンジンへと拡張していくことが、真の業務自動化エンジニアに求められる責務である。

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