【テクニカル・上級編】実務中級:マイルストーンタスクへの「終了日固定」制約の自動適用と検証 – Project VBA解析バイブル

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

Project VBAにおける制約条件の極致:マイルストーン制御とスケジュールの整合性検証におけるアーキテクチャ的アプローチ

プロジェクト管理ソフトウェアとしてのMicrosoft Projectは、その強力なスケジューリングエンジン(CPM: Critical Path Method)ゆえに、外部からVBAを用いて制御しようとする際、非常に繊細な挙動を示す。特に、大規模プロジェクトにおいて「特定の命名規則を持つマイルストーン」に対し、強制的な制約条件(Constraints)を付与し、かつその変更がスケジュール全体の整合性を破壊していないかを検証するプロセスは、単なるループ処理の積み重ねではない。

本稿では、オブジェクトのライフサイクル管理、COM参照の解放、そしてスケジューリングエンジンの論理的整合性を担保するための、プロフェッショナルな実装手法について詳述する。

1. スケジューリングエンジンの「不確実性」を制御する

MS Projectにおける制約条件、例えば `pjTaskFinishNoEarlierThan`(終了日指定)などは、エンジンの計算ロジックを直接的に上書きする。安易な自動適用は、クリティカルパスの不自然な歪みや、先行・後続関係の論理的破綻を招く。

シニアエンジニアが考慮すべきは、単に「値をセットする」ことではない。「制約を付与した後の再計算(Recalculate)が、意図したスケジュール・ドリフト(乖離)を引き起こしていないか」を、数学的に、あるいは論理的に検証する仕組みを構築することだ。

2. メモリ管理とCOMインターフェースの極限

VBAからMS Projectを操作する際、最も忌避すべきは「ゾンビプロセス」の発生である。大規模なタスクリスト(数万行規模)を処理する場合、オブジェクトの参照カウントが適切に制御されていないと、メモリリークが発生し、最終的にExcelやProjectのインスタンスが不安定化する。

`Set obj = Nothing` は基礎に過ぎない。実務レベルでは、`Marshal.ReleaseComObject` に相当する挙動を意識し、明示的にオブジェクトを解放する設計が求められる。

3. 実装:マイルストーン制約制御・検証エンジン

以下に、命名規則に基づいたマイルストーンへの制約適用と、その影響を検証するためのクラスモジュール設計のプロトタイプを示す。

Class Module: `clsMilestoneController`

Option Explicit

' Windows API: 処理時間の高精度計測用
Private Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long

Private mTaskCollection As Collection
Private mConstraintPattern As String
Private mTargetDate As Date

' 内部状態管理用
Private mIsErrorState As Boolean

Private Sub Class_Initialize()
    Set mTaskCollection = New Collection
    mIsErrorState = False
End Sub

''' <summary>
''' マイルストーン制御エンジンの初期化
''' </summary>
''' <param name="pattern">命名規則(例: "MS_*")</param>
''' <param name="fixedDate">適用する終了日</param>
Public Sub Initialize(ByVal pattern As String, ByVal fixedDate As Date)
    mConstraintPattern = pattern
    mTargetDate = fixedDate
End Sub

''' <summary>
''' プロジェクト内の対象タスクを走査し、制約を適用する
''' </summary>
Public Sub ApplyConstraints(ByRef targetApp As MSProject.Application)
    Dim t As MSProject.Task
    Dim startTime As Long
    
    startTime = GetTickCount()
    
    ' スケジューリングエンジンの自動計算を一時停止(パフォーマンス最適化)
    targetApp.Calculation = pjManual
    
    On Error GoTo ErrorHandler
    
    For Each t In targetApp.ActiveProject.Tasks
        If Not t Is Nothing Then
            ' 命名規則によるフィルタリング(Like演算子による高速判定)
            If t.Name Like mConstraintPattern Then
                ' マイルストーンであることを確認(期間が0)
                If t.Duration = 0 Then
                    ' 制約条件の適用: "Must Finish On" (pjTaskMustFinishOn)
                    ' 強力な制約をかけることで、スケジュール変動を強制的に抑え込む
                    t.ConstraintType = pjTaskMustFinishOn
                    t.Finish = mTargetDate
                    
                    ' 変更履歴をコレクションに保持(検証用)
                    mTaskCollection.Add t
                End If
            End If
        End If
    Next t

    ' 再計算を実行
    targetApp.Calculate
    targetApp.Calculation = pjAutomatic
    
    Debug.Print "Execution Time: " & (GetTickCount() - startTime) & " ms"
    Exit Sub

ErrorHandler:
    mIsErrorState = True
    targetApp.Calculation = pjAutomatic
    Err.Raise Err.Number, "clsMilestoneController", Err.Description
End Sub

''' <summary>
''' 制約適用後に、意図しないタスクの開始日が変動していないかを検証する
''' </summary>
Public Function ValidateScheduleIntegrity(ByRef targetApp As MSProject.Application) As Boolean
    Dim t As MSProject.Task
    Dim driftDetected As Boolean
    driftDetected = False
    
    ' 本来は、適用前のスナップショットと比較すべきだが、
    ' ここでは制約をかけたマイルストーン自体の整合性と、
    ' 後続タスクへの「不自然な押し出し」を簡易的にチェックするロジックとする
    
    For Each t In mTaskCollection
        ' 制約日と実際の終了日が一致しているか(エンジンの計算結果の検証)
        If Abs(DateDiff("d", t.Finish, mTargetDate)) > 0 Then
            driftDetected = True
            Exit For
        End If
    Next t
    
    ValidateScheduleIntegrity = Not driftDetected
End Function

''' <summary>
''' オブジェクトの明示的解放
''' </summary>
Public Sub Terminate()
    ' コレクション内の各オブジェクトに対する参照解除(重要)
    Dim i As Long
    For i = mTaskCollection.Count To 1 Step -1
        Set mTaskCollection(i) = Nothing
    Next i
    Set mTaskCollection = Nothing
End Sub

Standard Module: `modMain`

Option Explicit

''' <summary>
''' メイン実行ルーチン
''' </summary>
Public Sub ExecuteMilestoneControl()
    Dim projApp As MSProject.Application
    Dim controller As clsMilestoneController
    Dim targetDate As Date
    
    ' Projectインスタンスの取得
    On Error Resume Next
    Set projApp = MSProject.Application
    On Error GoTo 0
    
    If projApp Is Nothing Then
        MsgBox "MS Projectが起動していません。", vbCritical
        Exit Sub
    End If

    ' 設定値(実務では外部ファイルやDBから取得)
    targetDate = DateValue("2024-12-31")
    
    ' クラスのインスタンス化
    Set controller = New clsMilestoneController
    
    On Error GoTo Finalize
    
    ' 1. 初期化
    controller.Initialize("MS_*", targetDate)
    
    ' 2. 制約適用
    Debug.Print "Applying constraints..."
    controller.ApplyConstraints projApp
    
    ' 3. 検証
    Debug.Print "Validating integrity..."
    If Not controller.ValidateScheduleIntegrity(projApp) Then
        MsgBox "警告: 制約適用後にスケジュールの整合性が失われました。手動確認が必要です。", vbExclamation
    Else
        MsgBox "制約の適用と整合性検証が正常に完了しました。", vbInformation
    End If

Finalize:
    ' 4. 徹底したメモリ解放
    If Not controller Is Nothing Then
        controller.Terminate
        Set controller = Nothing
    End If
    
    If Err.Number <> 0 Then
        MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
    End If
End Sub

4. アーキテクチャの要諦:なぜこの設計なのか

4.1. 計算モードの制御 (`pjManual` vs `pjAutomatic`)

ループ内でタスクを一つ書き換えるたびにスケジューリングエンジンが走ると、計算量は $O(n^2)$ に近づき、パフォーマンスは壊滅的になる。`targetApp.Calculation = pjManual` によって、エンジンの介入を最小限に抑え、一括で `Calculate` を呼び出すのは、大規模プロジェクトを扱う際の鉄則である。

4.2. 制約の強度の選択

`pjTaskFinishNoEarlierThan`(終了日指定)は、エンジンの計算結果によって日付が後ろにずれることを許容する。一方で、`pjTaskMustFinishOn`(強制終了日)は、エンジンの計算結果を完全に無視する。実務において「マイルストーンは絶対に動かさない」という要求がある場合は、後者を選択せねばならないが、これは後続タスクに「負の遅延」を強制的に発生させるリスクを孕む。本コードでは、このリスクを検知するために `ValidateScheduleIntegrity` を設けている。

4.3. Windows APIによる計測

`GetTickCount` を用いた処理時間の計測は、単なるデバッグ用ではない。レガシーな環境や、ネットワークドライブ上の巨大な `.mpp` ファイルを扱う際、処理時間の異常な増大は、メモリリークやCOM参照の不備を検知するための重要なメトリクスとなる。

結論

VBAによるプロジェクト制御は、単なる自動化ツールではない。それは、スケジューリングエンジンという巨大な計算機に対する「命令系統の確立」である。オブジェクトのライフサイクルを掌握し、エンジンの論理的挙動を予測し、エラーを許容しない堅牢なコードを書くこと。それこそが、レガシーな環境から最新のエンタープライズ・アーキテクチャまでを生き抜く、シニアエンジニアの責務である。

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