スケジュール整合性の防衛線: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)」の極致である。
エンジニアが目指すべきは、コードが動くことではない。コードが、予期せぬユーザーの操作や設定変更に対しても、常に「正しい状態」を維持し続けることである。
