【実務・中級編】【プロ向け】TaskConstraintChangeイベントを用いた、ユーザーによる「制約条件の勝手な変更」を検知・復元する監視エージェント – Project VBA解析バイブル

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

スケジュール整合性の守護者:TaskConstraintChangeを用いた「制約条件監視エージェント」の設計論

プロジェクト管理において、ガントチャートは単なる「予定の羅列」ではない。それは、タスク間の依存関係、リソースの制約、そして計算ロジックが複雑に絡み合った数学的なモデルである。

現場のプロジェクトマネージャー(PM)が、局所的な遅延を隠蔽するために、あるタスクの制約条件を「ASAP(最短開始)」から「Must Start On(特定日開始)」へ勝手に書き換える。この「一見無害な操作」が、クリティカルパスを破壊し、プロジェクト全体の計算ロジックを崩壊させる。

本記事では、MS Project VBAにおいて、ユーザーによる制約変更をリアルタイムで検知し、不正な変更を即座にロールバック、あるいはログ記録する「監視エージェント」の構築手法を伝授する。これは単なるマクロではない。システムの整合性を担保するための防御的プログラミング(Defensive Programming)の実践である。

1. 陥落の罠:無限再帰(Infinite Recursion)の回避

まず、多くのジュニアエンジニアが陥る致命的な設計ミスについて触れておこう。

`TaskConstraintChange` イベントは、タスクの制約条件(ConstraintType)が変更された瞬間に発火する。もし、イベントハンドラの中で「不正な変更を元に戻す(=制約条件を書き換える)」という処理を書いた場合、どうなるか?

1. ユーザーが制約を変更する。
2. `TaskConstraintChange` イベントが発生する。
3. ハンドラ内で `Task.ConstraintType = 旧値` を実行する。
4. その書き換え自体が「制約変更」とみなされ、再び `TaskConstraintChange` が発生する。
5. ステップ3に戻り、無限ループに陥る。

結果、MS Projectはスタックオーバーフローを起こし、強制終了する。これを回避するためには、「再入防止フラグ(Re-entry Guard)」という概念が不可欠である。

2. アーキテクチャ設計:シャドウ・ステートの概念

堅牢な監視エージェントを構築するためには、以下の3つのコンポーネントが必要となる。

1. Authorized State (正当な状態の保持): どのタスクが、どのような制約を持つべきかという「正解」をメモリ上(または外部DB)に保持する。
2. Event Interceptor (イベント捕捉器): `TaskConstraintChange` をフックし、変更を検知する。
3. Rollback Engine (復元エンジン): 不正な変更を検知した際、再入防止フラグを立てた上で、正当な状態へ書き戻す。

3. プロダクション・コード実装例

以下に、実務レベルでそのまま利用可能な、カプセル化された監視エージェントのコードを示す。このコードは、`ThisProject` モジュール、あるいは専用のクラスモジュールに実装することを想定している。

事前準備

`Microsoft Scripting Runtime` への参照設定を推奨する(Dictionaryオブジェクトを使用するため)。

' --- ThisProject モジュールまたは専用の監視クラス内 ---
Option Explicit

' 再入防止フラグ:無限ループを防ぐための生命線
Private mIsProcessing As Boolean

' 正当な制約条件を保持するシャドウ・ステート(TaskID -> ConstraintType)
' 本来はDBや外部ファイルからロードすべきだが、ここではメモリ上で管理
Private mAuthorizedConstraints As Object 

''' <summary>
''' エージェントの初期化。正当な制約条件をロードする。
''' </summary>
Public Sub InitializeAgent()
    Set mAuthorizedConstraints = CreateObject("Scripting.Dictionary")
    
    ' --- 実務上のポイント ---
    ' ここでマスターデータ(CSVやSQL Server等)から「許可された制約」を読み込む設計にする。
    ' 以下はデモ用のハードコード。
    mAuthorizedConstraints.Add 5, pjTaskConstraintAsSoonAsPossible ' Task ID 5 は ASAP であるべき
    mAuthorizedConstraints.Add 10, pjTaskConstraintStartNoLaterThan ' Task ID 10 は 最遅開始 であるべき
    
    Debug.Print "Monitoring Agent Initialized."
End Sub

''' <summary>
''' タスクの制約条件変更イベントハンドラ
''' </summary>
''' <param name="Task">変更が加えられたタスクオブジェクト</param>
Private Sub Project_TaskConstraintChange(ByVal Task As Task)
    ' 1. 再入防止チェック
    If mIsProcessing Then Exit Sub
    
    ' 2. 処理開始フラグを立てる(ここから再帰が防止される)
    mIsProcessing = True
    
    On Error GoTo ErrorHandler
    
    ' 3. 監視対象のタスクか判定
    If mAuthorizedConstraints.Exists(Task.ID) Then
        Dim expectedConstraint As Long
        expectedConstraint = mAuthorizedConstraints(Task.ID)
        
        ' 4. 変更内容の検証
        If Task.ConstraintType <> expectedConstraint Then
            ' --- 不正な変更を検知 ---
            
            ' A. ログ記録(実務ではファイルやDBへ書き出す)
            Call LogViolation(Task, expectedConstraint, Task.ConstraintType)
            
            ' B. ロールバック(正当な状態へ強制書き戻し)
            ' mIsProcessing が True なので、この書き換えによる再発火は無視される
            Task.ConstraintType = expectedConstraint
            
            ' ユーザーへの警告(UXの観点から重要)
            MsgBox "警告: タスク '" & Task.Name & "' の制約条件はシステムによって保護されています。" & vbCrLf & _
                   "変更は自動的に取り消されました。", vbCritical, "整合性エラー"
        End If
    End If

CleanUp:
    mIsProcessing = False
    Exit Sub

ErrorHandler:
    Debug.Print "Error in TaskConstraintChange: " & Err.Description
    Resume CleanUp
End Sub

''' <summary>
''' 不正操作のログを出力する(拡張ポイント)
''' </summary>
Private Sub LogViolation(ByVal T As Task, ByVal Expected As Long, ByVal Actual As Long)
    Dim logMsg As String
    logMsg = Format(Now, "yyyy-mm-dd HH:mm:ss") & " | " & _
             "TaskID: " & T.ID & " | " & _
             "Name: " & T.Name & " | " & _
             "Expected: " & Expected & " | " & _
             "Actual: " & Actual
             
    ' 実務ではここで AppendText 等を用いて外部ログファイルへ書き出す
    Debug.Print "[VIOLATION] " & logMsg
End Sub

4. プロフェッショナルとしての設計留意点

このコードをプロダクション環境に投入する際、以下の3点について設計思想を持って臨んでほしい。

① データの永続性と信頼性 (Persistence)

上記の例では `Dictionary` をメモリ上で管理しているが、これではプロジェクトを閉じた瞬間に「正解」が消える。
実務においては、「プロジェクトのマスター定義ファイル(JSONやCSV)」、あるいは「中央管理のSQLデータベース」から、プロジェクト開始時に `InitializeAgent` を通じて状態をロードする設計にせよ。

② パフォーマンスの重み (Performance Overhead)

`TaskConstraintChange` は、ユーザーがマウスで制約をドラッグした際にも頻繁に発火する可能性がある。
監視対象のタスク(`mAuthorizedConstraints`)を絞り込んでいないと、巨大なプロジェクト(数万タスク規模)において、UIのレスポンスを著しく低下させる。「全てのタスクを監視するのではない。守るべきクリティカルなタスクのみを監視する」という、選択と集中の原則を忘れてはならない。

③ 権限分離の概念 (Separation of Concerns)

このエージェントは「強制力」を持つ。したがって、このコード自体をユーザーが容易に書き換えられないよう、アドイン(.xlam)形式で配布し、VBAプロジェクトにパスワードをかける、あるいはプロテクトされたDLLへロジックを逃がすといった、セキュリティレイヤーの検討も必要だ。

結言

自動化エンジニアの真の価値は、「ユーザーがやりたいことを実現する」ことだけではない。「ユーザーがやってはいけないことを、システムとして防ぐ」ことにある。

制約条件の監視エージェントは、プロジェクトの整合性を守るための「免疫システム」だ。この堅牢な設計思想を武器に、単なる効率化ツールを超えた、プロフェッショナルな業務基盤を構築してほしい。

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