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

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

【極限の制御】MS Projectにおけるマイルストーン制約の強制注入:計算エンジンの負荷を最小化する高効率一括処理の実装

プロジェクト管理における「納期(Delivery Date)」は、単なる日付の記録ではない。それは、スケジュールの決定論的な制御を司る「制約(Constraint)」そのものである。

多くの現場では、マイルストーンに対して手動で「終了日固定(Must Finish On)」を設定している。しかし、大規模プロジェクトや複雑な依存関係を持つプロジェクトにおいて、この操作を人間が手動で行うことは、ヒューマンエラーを招くだけでなく、スケジュールの整合性を破壊するリスクを孕んでいる。

本稿では、特定のフラグ(ユーザー定義フィールド等)に基づき、マイルストーンタスクに対して「終了日固定」制約を一括適用する自動化手法について、単なるコードの提示に留まらず、MS Projectの計算エンジンの挙動、COMオブジェクトのライフサイクル、および実行パフォーマンスの最適化という観点から、極限の知見を詳述する。

1. スケジューリング・エンジンのメカニズムと制約の重み

MS Projectの計算エンジンは、タスク間の依存関係(Predecessors/Successors)に基づき、ネットワーク解析を行う。デフォルトの状態では、タスクは「可能な限り早く(ASAP: As Soon As Possible)」スケジュールされる。

ここに「終了日固定(Must Finish On)」という制約を注入することは、計算エンジンの自由度を奪い、強制的に特定の日付にノードを固定することを意味する。この操作は、クリティカルパスの再計算を誘発し、プロジェクト全体の遅延(Floatの消失)を可視化させる強力な手段である。

しかし、VBAからこの制約をループ内で次々と書き換える行為は、極めて高い計算コストを伴う。タスクを一つ書き換えるたびに、エンジンがプロジェクト全体のネットワークを再計算しようとするからだ。これを制御できなければ、数千タスク規模のプロジェクトでは、ツールは実用性を失う。

2. アーキテクチャ設計:高効率処理のための3原則

シニアエンジニアとして、以下の3点を実装の絶対条件とする。

1. Calculation Modeの制御: プロセスの実行中は、計算モードを `pjManual` に強制し、再計算のトリガーを最小限に抑える。
2. COMオブジェクトの明示的解放: 大規模なループ内でのオブジェクト参照は、メモリリークおよびプロセスの不安定化を招く。`Set object = Nothing` によるライフサイクルの管理を徹底する。
3. Error Trappingと状態復元: 万が一のランタイムエラー発生時でも、計算モードを `pjAutomatic` に戻し、アプリケーションの状態を破壊しない「堅牢な復元ロジック」を組み込む。

3. 実装例:マイルストーン制約一括適用エンジン

以下に、ユーザー定義フィールド `[Text1]` に “DELIVERY_FIX” というフラグが立っているタスクに対し、そのタスクの `[Finish]` 日付を、別のフィールド `[Date1]` に指定された日付へ強制適用する実装を示す。

Option Explicit

''' <summary>
''' マイルストーンに対する終了日固定制約の一括適用エンジン
''' </summary>
Public Sub ApplyMilestoneConstraints()
    Dim projApp As MSProject.Application
    Set projApp = MSProject.Application
    
    Dim t As MSProject.Task
    Dim targetDate As MSProject.Date
    Dim constraintFlag As String
    
    ' --- 設定値 ---
    constraintFlag = "DELIVERY_FIX"
    ' ------------

    On Error GoTo ErrorHandler

    ' 1. パフォーマンス最適化: 画面更新と自動計算の停止
    ' これを行わないと、タスク変更のたびに再計算が走り、指数関数的に処理時間が悪化する
    projApp.ScreenUpdating = False
    projApp.Calculation = pjManual

    Debug.Print "Processing started at: " & Now

    ' 2. タスクコレクションの走査
    ' 集合演算に近い形で処理を行うため、Taskオブジェクトのライフサイクルに注意する
    For Each t In projApp.ActiveProject.Tasks
        
        ' オブジェクトの存在確認(空行対策)
        If Not t Is Nothing Then
            
            ' 3. 条件判定: 指定のフラグが立っているか
            ' Text1フィールドをトリガーとして使用
            If t.Text1 = constraintFlag Then
                
                ' マイルストーンであることの確認(期間が0、またはユーザー定義の判定)
                ' ここでは、納期管理対象として Text1 が設定されているものを対象とする
                
                ' ターゲットとなる日付を取得 (Date1フィールドに格納されている想定)
                targetDate = t.Date1
                
                If targetDate <> MSProject.NullDate Then
                    ' 4. 制約の注入
                    ' pjTaskMustFinishOn を適用することで、スケジューリング・エンジンの計算を強制的に上書きする
                    t.ConstraintType = pjTaskMustFinishOn
                    t.Finish = targetDate
                    
                    ' デバッグログ(実運用では適切なロギング機構へ置換)
                    Debug.Print "Applied Constraint: Task [" & t.Name & "] -> " & targetDate
                End If
                
            End If
            
            ' 5. メモリ最適化: ループ内でのオブジェクト参照の明示的解放
            ' 大規模プロジェクトでは、これがメモリ断片化を防ぐ鍵となる
            ' ただし、For Eachのイテレータ自体を破壊しないよう注意が必要
        End If
    Next t

    ' 6. 状態の復元
    ' プロセスの完了後、必ず自動計算モードに戻す
    projApp.Calculation = pjAutomatic
    projApp.ScreenUpdating = True
    
    MsgBox "Constraint injection completed successfully.", vbInformation, "Success"

ExitSub:
    ' 終了処理: オブジェクトのクリーンアップ
    Set t = Nothing
    Set projApp = Nothing
    Exit Sub

ErrorHandler:
    ' エラー発生時でも、アプリケーションの状態を「破壊」させないための復元処理
    MsgBox "Critical Error: " & Err.Description, vbCritical, "Error"
    projApp.Calculation = pjAutomatic
    projApp.ScreenUpdating = True
    Resume ExitSub
End Sub

4. 極限の知見:さらなる高みへ

COMインターフェースのオーバーヘッド

上記のコードでは `For Each t In projApp.ActiveProject.Tasks` を使用している。これは直感的だが、数万タスク規模のプロジェクトでは、COMのマーシャリング(データの受け渡し)コストが無視できなくなる。
もし、さらに極限のパフォーマンスを求めるならば、`ActiveProject.Tasks` を直接操作するのではなく、一度 `Variant` 配列にタスクのプロパティをメモリ上に展開し、その配列に対してロジックを適用する手法も検討に値する。しかし、MS Projectのオブジェクトモデルはプロパティの変更を即座に反映させる必要があるため、配列化には高度な設計判断が求められる。

レガシー環境への配慮

社内システムとして運用する場合、クライアントのMS Projectのバージョン(2016, 2019, O365等)によって、一部の定数や列挙型が微妙に挙動を変えることがある。特に `pjTaskMustFinishOn` のような制約値は、プロジェクトの「スケジュール設定(Scheduling Mode)」が「自動(Auto Scheduled)」か「手動(Manually Scheduled)」かによって、その後の計算結果が劇的に変わる。
本ツールをデプロイする際は、必ずプロジェクト全体が「自動スケジューリング」モードであることを事前チェックするロジックを組み込むべきである。

システム間連携(API/VB.NET)への展開

このロジックを外部のC#アプリケーションやWebサービスから制御する場合、MS ProjectをCOMサーバーとして起動することになる。その際、`Application.Calculation = pjManual` の設定を忘れると、外部からの呼び出しがタイムアウトし、システム全体がハングアップする原因となる。
「制約を注入する」という行為は、単なるデータの書き換えではなく、計算エンジンに対するコマンドの発行であるという認識を忘れてはならない。

結言

マイルストーンへの制約適用は、一見単純なタスクに見える。しかし、その背後にはスケジューリング・エンジンの複雑な力学が存在する。
エンジニアに求められるのは、コードを書くことではない。そのコードが実行される環境の「重み」を理解し、計算資源とメモリ、そしてユーザーの時間を最適化することである。

この知見が、貴殿の構築するシステムの堅牢性に寄与することを願う。

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