エンジンの「暴走」を制御せよ:MS Project VBAにおける計算エンジン掌握術
多くの開発者が、MS ProjectのVBA自動化において「致命的なミス」を犯している。
それは、Projectの計算エンジンを「ブラックボックス」として扱い、ただデータを流し込むだけのスクリプトを書いていることだ。
数千タスク規模のプロジェクトを扱う実務ツールにおいて、タスクの開始日を一つ書き換えるたびにエンジンが再計算を開始し、画面がフリーズする。あるいは、制約条件(Constraint)の不整合により、意図しない日付へとスケジュールが「跳ねる」。これらはすべて、エンジンのライフサイクルと制御オプションを理解していないことによる、設計上の敗北である。
プロフェッショナルなツールを作るなら、Projectを単なる「データ格納庫」として見るな。「計算ロジックを持つ動的なエンジン」として制御下に置け。
本稿では、計算精度の最適化とパフォーマンスの極大化を実現するための、エンジンの隠し設定(Options)と制約条件の動的制御について、設計思想から実装までを伝授する。
1. 計算エンジンの「停止」と「一括実行」:パフォーマンスの生命線
MS Projectの最大の特徴であり、最大の弱点は、データの変更に対して「即時計算」を行おうとする性質にある。
ループ内で1,000個のタスクの期間を更新するコードを書いたとしよう。標準的な書き方では、1,000回の再計算が発生する。計算量(Complexity)がタスク数の二乗に比例するネットワーク構造において、これは指数関数的な遅延を招く。
アーキテクチャの鉄則:計算の「アトミック化」
エンジニアが取るべき戦略は、「計算の停止 $\to$ データの一括更新 $\to$ 設定の最適化 $\to$ 計算の強制実行」という、一連のトランザクションとして処理を完結させることだ。
' 悪い例:ループ内で計算が走ってしまう
For Each t In ActiveProject.Tasks
t.Duration = t.Duration + 1 ' ここで毎回計算が発生し、大規模プロジェクトでは死に至る
Next t
2. 制約条件(Constraint)の制御:ASAP/ALAPの真実
スケジュール計算において、`ConstraintType`(制約の種類)の制御は、エンジンの挙動を決定づける最重要パラメータだ。
多くの開発者は、`Start` プロパティを書き換えるだけで済ませようとする。しかし、それは誤りだ。プロパティを直接書き換えると、Projectは暗黙的に「Must Start On(指定した日に開始しなければならない)」といった強力な制約を付与してしまうことがある。
これが、後続タスクが連鎖的に動かなくなる「スケジュール硬直化」の原因だ。
- ASAP (As Soon As Possible): 制約を `pjAsSoonAsPossible` に設定し、エンジンの論理計算にすべてを委ねる。
- ALAP (As Late As Possible): 制約を `pjAsLateAsPossible` に設定し、クリティカルパスの余裕(Slack)を最大限に活用する。
これらを動的に切り替えることで、プロジェクトの「バッファ」をプログラムからコントロール可能にする。
3. プロダクション・レディな実装:`ScheduleEngineController`
以下に、実務でそのまま利用可能な、堅牢な制御クラスの設計例を示す。単なる関数ではなく、エラーハンドリングと状態復旧(State Recovery)を組み込んだ、プロフェッショナル仕様のコードだ。
''
' @class ScheduleEngineController
' @description MS Projectの計算エンジンと制約条件を高度に制御するためのコントローラー
''
Public Class ScheduleEngineController
Private m_OriginalCalculation As Long
Private m_IsProcessing As Boolean
' 処理開始:エンジンの計算を停止し、現在の状態を保存する
Public Sub BeginTransaction()
On Error GoTo ErrorHandler
m_IsProcessing = True
' 現在の計算モードを保存
m_OriginalCalculation = Application.Calculation
' 計算をマニュアルモードに切り替え、再計算のオーバーヘッドを排除
Application.Calculation = pjManual
' 画面更新を停止(描画負荷の軽減)
Application.ScreenUpdating = False
Exit Sub
ErrorHandler:
m_IsProcessing = False
Err.Raise Err.Number, "ScheduleEngineController.BeginTransaction", Err.Description
End Sub
' 処理終了:エンジンの再計算を実行し、状態を元に戻す
Public Sub EndTransaction()
On Error GoTo ErrorHandler
' 1. データの整合性を確保するため、ここで一括計算を実行
Application.Calculate
' 2. 設定を元に戻す
Application.Calculation = m_OriginalCalculation
Application.ScreenUpdating = True
m_IsProcessing = False
Exit Sub
ErrorHandler:
' エラー時でも、計算モードがManualのまま放置されるとユーザーが混乱するため、必ず復旧させる
Application.Calculation = pjAutomatic
Application.ScreenUpdating = True
m_IsProcessing = False
Err.Raise Err.Number, "ScheduleEngineController.EndTransaction", Err.Description
End Sub
' タスクの制約条件を安全に設定する
' @param t 対象タスク
' @param cType 制約タイプ (pjAsSoonAsPossible, pjAsLateAsPossible 等)
' @param constraintDate 制約日付(必要な場合のみ)
Public Sub SetSafeConstraint(t As Task, cType As Long, Optional constraintDate As Date = 0)
If t Is Nothing Then Exit Sub
On Error GoTo ErrorHandler
' 制約タイプをまず適用
t.ConstraintType = cType
' 日付が指定されている場合は、その日付を制約としてセット
If constraintDate <> 0 Then
t.ConstraintDate = constraintDate
End If
Exit Sub
ErrorHandler:
' 制約の不整合(例:Must Start On が既に設定されている等)によるエラーを捕捉
Debug.Print "Constraint Error on Task: " & t.Name & " - " & Err.Description
End Sub
End Class
このコードが「プロ仕様」である理由
1. 状態の保存と復旧 (State Recovery): `m_OriginalCalculation` に元の設定を保持している。これにより、ユーザーが元々「自動計算」にしていたのか「手動」にしていたのかを問わず、処理終了後に適切な状態へ復元できる。
2. エラーハンドリングの徹底: `EndTransaction` が失敗した際に、計算モードが `pjManual` のまま放置されることを防いでいる。これは、ツールを配布した後の「サポートコスト」を劇的に下げる設計だ。
3. 計算の集約: `Application.Calculate` を明示的に呼び出すことで、データの整合性が完全に取れた状態で、一度だけ高負荷な計算を走らせている。
4. 外部データベース連携における設計上の注意点
もし君が、SQL ServerやExcelなどの外部ソースからデータを流し込むツールを作っているなら、以下の設計原則を忘れてはならない。
「Dirty Data」の隔離
データベースから取得したデータが、必ずしもProjectのロジックに適合するとは限らない。
- Validation Layer: データを `Task` オブジェクトに書き込む前に、日付の前後関係や、制約条件との矛盾をメモリ上で検証せよ。
- Batch Update: データベースとの通信(I/O)と、Projectのエンジン制御(CPU/Memory)は分離せよ。先に全データをメモリ上の構造体に読み込み、その後 `BeginTransaction` を呼び出してProjectへ流し込むのが定石だ。
ライフサイクルの同期
DBのレコード更新とProjectの計算結果の同期は、「Projectの再計算完了後」に行うべきだ。計算前のデータに基づいてDBを更新してしまうと、Project側で発生したスケジュールのズレ(スラックの消費など)をDBに反映できなくなる。
結論:エンジンの支配者であれ
MS Project VBAの極致は、APIのメソッドを並べることではない。「いつ、どのタイミングで、どの程度の精度でエンジンを回すか」を、開発者が完全にコントロールすることにある。
計算モードを制御し、制約条件を論理的に扱い、エラー発生時でもシステムの整合性を保つ。この「エンジンの制御能力」こそが、単なるスクリプターと、伝説的なアーキテクトを分かつ境界線である。
次回のコードを書くときは、自問自え。
「今、この一行の書き換えで、エンジンは何回再計算を繰り返しているか?」と。
