【テクニカル・上級編】【プロ向け】計算エンジンの「再計算保留」状態を監視し、ユーザーに警告を出す仕組み – Project VBA解析バイブル

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

スケジュール整合性の防衛線:Project VBAにおける計算エンジン状態の監視と制御

プロジェクト管理における致命的なエラーは、多くの場合、計算ロジックの欠如ではなく、「計算が実行されていないという事実」から生じる。

Microsoft Projectの計算エンジン(Scheduling Engine)は、タスク間の依存関係や制約条件を解釈し、クリティカルパスを導き出す心臓部である。しかし、大規模なプロジェクトや複雑なマクロ操作を行う現場では、パフォーマンス確保のために計算モードを「手動(Manual)」に切り替える運用が常態化している。

この「手動モード」は諸刃の剣だ。ユーザーが意図的に制御している間は有用だが、VBAによるデータ操作(日付の強制書き換えや制約の変更)が行われた直後に再計算が実行されない場合、プロジェクトの論理構造は即座に破綻する。不整合なスケジュールに基づいた意思決定は、プロジェクトの破滅を意味する。

本稿では、シニアエンジニアが備えておくべき、計算エンジンの状態をバックグラウンドで監視し、整合性を担保するための堅牢な監視アーキテクチャについて詳述する。

1. 脆弱性の本質:計算モードの不一致

Project VBAにおいて、`Application.Calculation` プロパティは、計算エンジンの動作を制御する。

  • `pjCalculationAutomatic` (0): 変更のたびにエンジンが稼働。
  • `pjCalculationManual` (1): ユーザーまたはコードによる明示的な指示が必要。

問題は、マクロが「タスクの開始日を書き換えた」にもかかわらず、計算モードが `Manual` であるために、後続タスクの開始日が更新されない状態(Dirty State)が発生することだ。これを検知せずに処理を続行することは、設計上の欠陥と言わざるを得ない。

2. アーキテクチャ設計:非ブロッキング・ポーリング・モデル

単純な `Do While` ループによる監視は、Projectのメインスレッドを占有し、UIをフリーズさせる。これは実用的なツールとは呼べない。

我々が実装すべきは、「Windows APIを活用したタイマー制御」、あるいは「Application.OnTime(Projectにおける再帰的イベント呼び出し)」を用いた、非ブロッキングな監視メカニズムである。今回は、レガシー環境での安定性と、Projectのイベントループを破壊しない安全性を考慮し、`Application.OnTime` をベースとしたクラスモジュールによるカプセル化手法を採用する。

設計の要件

1. 疎結合な監視: 監視ロジックはメインの業務ロジックから分離されていること。
2. メモリ管理の厳格化: オブジェクトのライフサイクルを制御し、メモリリークを徹底的に排除すること。
3. 低オーバーヘッド: 監視プロセスが計算エンジン自体のパフォーマンスを阻害しないこと。

3. 実装:`clsScheduleIntegrityMonitor`

以下に、監視ロジックをカプセル化したクラスモジュールの設計例を示す。

クラスモジュール: `clsScheduleIntegrityMonitor`

VERSION 1.0 CLASS
' ==============================================================================
' Class: clsScheduleIntegrityMonitor
' Description: プロジェクトの計算モードを監視し、不整合を防止するガードクラス
' Author: Chief Architect
' ==============================================================================

Option Explicit

' 監視の状態を管理するプライベート変数
Private m_isMonitoring As Boolean
Private m_nextCheckTime As Date

' 定数定義
Private Const CHECK_INTERVAL_SECONDS As Integer = 5 ' 監視間隔(秒)

' 外部から監視を開始するためのメソッド
Public Sub StartMonitoring()
    If m_isMonitoring Then Exit Sub
    
    m_isMonitoring = True
    Debug.Print "[Monitor] Integrity monitoring started."
    ScheduleNextCheck
End Sub

' 監視を停止するためのメソッド
Public Sub StopMonitoring()
    m_isMonitoring = False
    Debug.Print "[Monitor] Integrity monitoring stopped."
End Sub

' メインの監視ロジック
' このメソッドは Application.OnTime によって非同期的に呼び出される
Public Sub CheckIntegrity()
    On Error GoTo ErrorHandler

    ' 監視が停止していれば終了
    If Not m_isMonitoring Then Exit Sub

    ' 計算モードの検証
    ' pjCalculationManual = 1
    If Application.Calculation = 1 Then
        ' 手動モードが検知された場合、ユーザーに警告
        ' ※実運用では、業務ロジックの実行中かどうかを判定するフラグを併用すべき
        MsgBox "【警告】プロジェクトの計算モードが『手動』に設定されています。" & vbCrLf & _
               "スケジュールの一貫性を保つため、再計算を実行してください。", _
               vbExclamation, "スケジュール整合性警告"
        
        ' 警告後も監視を継続するか、あるいは停止するかはポリシー次第
        ' ここでは安全のため、一度警告を出した後は監視を継続する
    End If

    ' 次回のチェックをスケジュール
    ScheduleNextCheck
    Exit Sub

ErrorHandler:
    ' エラー発生時も監視を完全に停止させるのではなく、ログを残して再試行する
    Debug.Print "[Monitor] Error encountered: " & Err.Description
    m_isMonitoring = False
End Sub

' 再帰的なスケジュール実行
Private Sub ScheduleNextCheck()
    If m_isMonitoring Then
        m_nextCheckTime = Now + TimeSerial(0, 0, CHECK_INTERVAL_SECONDS)
        ' Application.OnTime を利用して、Projectのイベントループを妨げずに次回の呼び出しを予約
        Application.OnTime m_nextCheckTime, "MonitorEntryGate"
    End If
End Sub

' オブジェクトの破棄(メモリ解放)
Private Sub Class_Terminate()
    m_isMonitoring = False
    Set m_nextCheckTime = Nothing
    Debug.Print "[Monitor] Object destroyed and memory released."
End Sub

標準モジュール: `modMonitorBridge`

`Application.OnTime` はクラスのメソッドを直接呼び出すことができないため、ブリッジとなる標準モジュールが必要となる。

Option Explicit

' モニタークラスのインスタンスを保持するグローバル変数
' ライフサイクルを制御するため、明示的に管理する
Private m_monitor As clsScheduleIntegrityMonitor

' モニターの起動
Public Sub InitializeIntegrityGuard()
    ' 既存のインスタンスがあれば解放(メモリリーク防止)
    If Not m_monitor Is Nothing Then
        m_monitor.StopMonitoring
        Set m_monitor = Nothing
    End If
    
    Set m_monitor = New clsScheduleIntegrityMonitor
    m_monitor.StartMonitoring
End Sub

' モニターの停止
Public Sub TerminateIntegrityGuard()
    If Not m_monitor Is Nothing Then
        m_monitor.StopMonitoring
        Set m_monitor = Nothing
    End If
End Sub

' Application.OnTime から呼び出されるエントリーポイント
' クラスのメソッドへディスパッチする
Public Sub MonitorEntryGate()
    If Not m_monitor Is Nothing Then
        m_monitor.CheckIntegrity
    End If
End Sub

4. 極限の知見:プロフェッショナルが留意すべき3つのポイント

このコードを実戦投入する際、シニアエンジニアは以下の「重み」を理解していなければならない。

① メモリ管理とオブジェクトのライフサイクル

VBAにおける `Set m_monitor = Nothing` は、単なるお作法ではない。特に `Application.OnTime` を利用している場合、オブジェクトがメモリ上に残り続けていると、バックグラウンドで予約されたタイマーが「存在しないオブジェクト」を呼び出そうとし、ランタイムエラーを引き起こす。監視を開始する前に必ず既存のインスタンスを破棄し、終了時には確実に `Stop` を呼ぶ。これが「決定論的なシステム」を構築する鉄則だ。

② 計算モード監視の「副作用」への配慮

本実装では `MsgBox` を使用している。しかし、大規模なバッチ処理(例:数千タスクの更新)の最中にこの警告が出ると、ユーザーの作業を中断させ、最悪の場合、バッチ処理のタイムアウトやデッドロックを招く。
実務レベルでは、「特定の業務マクロ実行中(IsProcessingフラグ)は監視を一時停止する」、あるいは 「MsgBoxではなく、ステータスバーへの表示やログ出力に留める」 といった、コンテキストに応じた制御ロジックを組み込むべきである。

③ Windows APIへの拡張性

もし、より高精度な(ミリ秒単位の)監視が必要、あるいはProjectのメインスレッドを完全に分離して動作させたい場合は、`SetTimer` APIを用いたマルチスレッド的なアプローチを検討する。ただし、VBAはシングルスレッド・アーキテクチャであるため、APIによるタイマー割り込みは、実行中のコードのスタックを破壊するリスクを孕む。
「安定性」と「精度」のトレードオフを理解し、プロジェクトの要求仕様に応じて、`OnTime`(安全・低精度)か `SetTimer`(危険・高精度)かを選択する判断力が、アーキテクトには求められる。

結論

スケジュール管理システムにおいて、計算エンジンの状態を監視することは、単なる「親切な機能」ではない。それは、データの整合性を守り、システムに対する信頼を担保するための「防御的プログラミング(Defensive Programming)」の極致である。

エンジニアが目指すべきは、コードが動くことではない。コードが、予期せぬユーザーの操作や設定変更に対しても、常に「正しい状態」を維持し続けることである。

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