【テクニカル・上級編】【実務中級】「期限(Deadline)」設定と「終了日固定」制約の併用による、進捗遅延リスクの早期アラートシステム – Project VBA解析バイブル

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

制約の歪みを監視せよ:Deadlineと制約条件の乖離による「遅延予兆検知エンジン」の構築

プロジェクト管理の現場において、MS Projectの「制約条件(Constraints)」を過信しているエンジニアは多い。
「終了日固定(Finish No Later Than)」を設定すれば、スケジュールは守られると信じている。だが、それは致命的な誤解だ。

制約条件は、スケジュールの計算エンジンに対して「強制的」な命令を下す。しかし、それはプロジェクト全体の論理的な整合性を歪め、クリティカルパスの真の姿を隠蔽する。制約によって無理やり日付を固定した結果、後続タスクが不自然に押し出され、プロジェクトの「真の遅延リスク」が計算エンジンから見えなくなる。これが、プロジェクトが崩壊する直前の「静かなる予兆」だ。

本稿では、制約条件による「スケジュールの歪み」を検知するため、「Deadline(期限)」と「Constraint(制約)」の二重構造を用いた、高精度なリスクアラートシステムの設計・実装について解説する。

1. アーキテクチャの核心:制約(Constraint)と期限(Deadline)の分離

まず、我々が制御すべき二つの概念を定義し直す必要がある。

1. 制約条件 (Constraint):
計算エンジン(Scheduling Engine)に対する「強制命令」。`pjTaskFinishNoLaterThan` などの制約は、タスクの終了日を強制的に固定し、計算ロジックをバイパスさせる。これは「結果」を規定するものであり、「プロセス」の健全性を保証しない。
2. 期限 (Deadline):
計算エンジンには影響を与えない「監視指標」。タスクの計算結果がこの日付を超えた場合、MS Projectは視覚的な警告(赤い線)を表示するが、スケジュール自体は動かさない。これは「理想」を規定するものである。

真のリスクは、この「強制された結果(Constraint)」と「守るべき理想(Deadline)」の乖離、および「現在の計算結果(Finish)」の三者関係の崩壊に潜んでいる。

我々が構築するシステムは、以下のロジックでリスクを判定する。
> Risk Threshold < (Deadline - Finish) < Constraint Gap

もし、制約条件によって終了日が無理やり抑え込まれ、Deadlineとの差分が縮小している場合、それは「制約の力だけでプロジェクトを支えている」状態であり、極めて脆弱なフェーズにあることを意味する。

2. 実装:高パフォーマンス・リスクモニタリング・エンジン

大量のタスク(数千規模)を扱う場合、愚直なループは致命的なパフォーマンス低下を招く。MS ProjectのCOMオブジェクトへのアクセスはオーバーヘッドが大きいため、計算モードの制御と、メモリ管理の徹底が不可欠だ。

以下に、シニアエンジニアが実戦で投入すべき、堅牢なVBAモジュールの設計例を示す。

Option Explicit

' --- Windows API による高精度な時間計測(プロファイリング用) ---
Private Declare PtrSafe Function GetTickCount64 Lib "kernel32" () As LongLong

''' <summary>
''' プロジェクト内のリスクをスキャンし、制約と期限の乖離を特定する
''' </summary>
Public Sub ExecuteRiskAudit()
    Dim app As MSProject.Application
    Dim t As MSProject.Task
    Dim startTime As LongLong
    Dim riskCount As Long
    
    ' プロジェクトアプリケーションへの参照取得
    Set app = MSProject.Application
    
    ' パフォーマンス最適化: 計算モードをマニュアルに切り替え、
    ' プロパティへのアクセスによる再計算の連鎖を防ぐ
    app.Calculation = pjCalculationManual
    
    startTime = GetTickCount64
    riskCount = 0
    
    On Error GoTo ErrorHandler

    ' トランザクションの開始(読み取り専用操作の高速化)
    ' ※大規模データの場合、ここでのメモリ管理が鍵となる
    
    Debug.Print "--- Risk Audit Started ---"

    ' Taskコレクションの反復処理
    ' 注意: Collectionの要素を直接操作するのではなく、
    ' オブジェクトのライフサイクルを意識して変数に格納する
    For Each t In app.ActiveProject.Tasks
        If Not t Is Nothing Then
            ' スキップ条件: サマリータスクや完了タスクは対象外とする設計
            If Not t.Summary And Not t.Completed Then
                
                ' リスク判定ロジックの呼び出し
                If CheckTaskRisk(t) Then
                    riskCount = riskCount + 1
                    ' 警告ログの出力(実運用ではExcelやDBへ書き出し)
                    Debug.Print "[RISK DETECTED] Task: " & t.Name & " | Finish: " & t.Finish & " | Deadline: " & t.Deadline
                End If
                
            End If
        End If
        
        ' メモリリーク防止: 大規模ループ内でのオブジェクト参照の明示的解放
        ' (VBAのGCに依存せず、エンジニアの責任で制御する)
        ' Set t = Nothing ' For Each の場合は不要だが、構造によっては必須
    Next t

    ' 計算モードを自動に戻す
    app.Calculation = pjCalculationAutomatic
    
    Debug.Print "--- Audit Completed. Risks Found: " & riskCount & " ---"
    Debug.Print "Execution Time: " & (GetTickCount64 - startTime) & " ms"

ExitSub:
    ' クリーンアップ
    Set app = Nothing
    Exit Sub

ErrorHandler:
    MsgBox "Critical Error in Risk Audit: " & Err.Description, vbCritical
    app.Calculation = pjCalculationAutomatic
    Resume ExitSub
End Sub

''' <summary>
''' 個別のタスクに対して、制約と期限の乖離を検証する
''' </summary>
''' <param name="t">対象のTaskオブジェクト</param>
''' <returns>リスクありの場合はTrue</returns>
Private Function CheckTaskRisk(ByRef t As MSProject.Task) As Boolean
    Dim deadlineGap As LongPtr
    Dim constraintGap As LongPtr
    
    ' 1. Deadlineが設定されているか確認
    If t.Deadline = 0 Then Exit Function ' Deadline未設定は監視対象外
    
    ' 2. 制約条件のタイプを確認
    ' pjFinishNoLaterThan (終了日固定) 等の「強制力が強い制約」のみを対象とする
    If t.ConstraintType <> pjTaskFinishNoLaterThan And _
       t.ConstraintType <> pjTaskStartNoEarlierThan Then
        Exit Function
    End If

    ' 3. 日付差分(Gap)の計算
    ' MS Projectの内部日付シリアル値を利用して高速計算
    ' DeadlineとFinishの差分
    deadlineGap = DateDiff("d", t.Finish, t.Deadline)
    
    ' 制約日付(ConstraintDate)とFinishの差分
    ' 制約によってFinishが無理やり抑え込まれている場合、このGapは極めて小さくなる
    constraintGap = DateDiff("d", t.Finish, t.ConstraintDate)

    ' --- 判定ロジック ---
    ' 条件A: Deadlineが目前に迫っている (例: 5日以内)
    ' 条件B: かつ、制約条件がそのDeadlineを「追い越して」適用されている
    ' これにより、制約によってスケジュールが「偽装」されている状態を検知する
    If deadlineGap <= 5 And constraintGap <= 0 Then
        CheckTaskRisk = True
    Else
        CheckTaskRisk = False
    End If
End Function

3. エンジニアリングの極致:メモリとパフォーマンスへの洞察

このコードを実務のレガシー環境、あるいは大規模なエンタープライズ環境で運用するにあたり、以下の3点を遵守せよ。

I. COMオブジェクトのライフサイクル管理

VBAはガベージコレクション(GC)が極めて脆弱だ。`For Each` ループ内で複雑なオブジェクト操作を行う場合、参照カウントが意図せず増大し、MS Projectが「ゾンビプロセス」としてメモリに居座る原因となる。大規模な自動化を行う際は、常に `Set obj = Nothing` を意識し、スコープを最小限に留めることが、システムの長時間稼働における鉄則である。

II. 計算エンジンの「静止」

`app.Calculation = pjCalculationManual` は単なる高速化テクニックではない。これは、プロパティへのアクセス(例:`t.Finish`)のたびに発生する「計算グラフの再走査」という膨大なコストを遮断するための、アーキテクチャ上の防壁である。

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

`GetTickCount64` を用いた計測をコードに組み込んでいるのは、単なる趣味ではない。業務自動化において、処理時間の増大は「スケーラビリティの欠如」を意味する。実行時間をログに記録し続けることで、プロジェクトの規模(タスク数)が増大した際に、いつアルゴリズムの限界が来るかを予測可能にするのだ。

結論

「制約条件」は、プロジェクト管理における「麻薬」だ。一時的にスケジュールを整え、見栄えを良くするが、その代償として真のリスクを隠蔽する。

真のアーキテクトは、制約に頼るのではなく、「制約が作り出す歪み」を監視するシステムを構築する。Deadlineという「理想」と、Constraintという「強制」、そして計算結果という「現実」の三点を、VBAという低レイヤーの武器を使いこなして紐解く。それこそが、プロジェクトの崩壊を未然に防ぐ唯一の手段である。

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