Project VBAの深淵:大規模プロジェクトにおけるカスタムフィールド計算負荷の極限最適化
MS Projectのカスタムフィールドに複雑な数式を仕込み、タスク数が数千件を超えたあたりで、多くのエンジニアが「計算中…」という絶望的なプログレスバーに直面する。
初心者は「PCのスペックを上げればいい」と考え、中級者は「数式を簡素化すればいい」と考える。だが、我々アーキテクトは知っている。問題はスペックでも数式の複雑さでもない。MS Projectの再計算エンジンの「トリガー連鎖」と、COMオブジェクトのオーバーヘッドにある。
本稿では、カスタムフィールドの計算負荷を極限まで抑え、大規模プロジェクトにおいてもミリ秒単位のレスポンスを実現するための、泥臭くも高潔な最適化手法を伝授する。
1. 「カスタムフィールド数式」という罠を捨てる
まず、大前提として、大規模プロジェクトにおいて「カスタムフィールドの[数式]プロパティ」に直接ロジックを書き込むことは、自殺行為である。
Projectの数式エンジンは、依存関係にある値が一つでも変われば、全タスクに対して再評価を試みる。この「カスケード更新」が、UIスレッドをロックさせ、操作不能なラグを生む正体だ。
アーキテクチャの転換:Push型計算への移行
数式による「自動計算(Pull型)」を捨て、VBAによる「明示的計算(Push型)」に切り替えろ。
1. フィールド定義を「空」にする: 数式プロパティを完全に削除し、単なるデータ格納領域(静的フィールド)として定義する。
2. 計算タイミングを制御する: `Task`オブジェクトの変更イベント、あるいは特定のバッチ処理タイミングでのみ計算を実行する。
3. 差分更新の実装: 値に変更があったタスクのみを更新し、不必要な書き込みを徹底的に排除する。
2. 極限のパフォーマンスを引き出す実装戦略
以下に、数千件のタスクを持つ環境でも動作を軽快に保つための、最適化された実装例を示す。ここでは、Windows APIを用いて描画負荷を完全に遮断し、COMオブジェクトのライフサイクルを厳格に管理する。
最適化実装コード
Option Explicit
' --- Windows API Declarations ---
' 画面描画の完全停止と再開を制御し、OSレベルでの再描画負荷を削減する
Private Declare PtrSafe Function LockWindowUpdate Lib "user32" (ByVal hWndLock As LongPtr) As Long
Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As LongPtr, ByVal Msg As Long, ByVal wParam As LongPtr, ByVal lParam As Any) As LongPtr
Private Const WM_SETREDRAW As Long = &HB
''' <summary>
''' 大規模プロジェクトにおけるカスタムフィールドの一括計算最適化処理
''' </summary>
Public Sub OptimizeCustomFieldCalculation()
On Error GoTo ErrorHandler
Dim t As Task
Dim projectApp As MSProject.Application
Set projectApp = Application
' 1. UI更新の完全停止 (Project標準 + Windows API)
' ProjectのScreenUpdatingだけでは不十分なケースが多い
projectApp.ScreenUpdating = False
LockWindowUpdate 0 ' デスクトップ全体の更新をロック(慎重に使用すること)
' 2. 計算モードの擬似的な制御
' ProjectにはExcelのようなCalculation = xlManualがないため、
' 書き込み回数を最小限に抑える戦略をとる
Dim taskCount As Long
taskCount = projectApp.ActiveProject.Tasks.Count
Debug.Print "Processing " & taskCount & " tasks..."
' 3. 高速ループ実装
' Taskコレクションを直接回すのではなく、インデックスでアクセスする方が
' 特定の環境ではCOMのオーバーヘッドを削減できる
Dim i As Long
For i = 1 To taskCount
Set t = projectApp.ActiveProject.Tasks(i)
' 空タスク(サマリー以外で割り当てがない等)をスキップしてメモリ負荷を軽減
If Not t Is Nothing Then
If Not t.Summary Then
' 【極めて重要】値の変更がある場合のみ書き込む
' Projectは値が同一でも書き込みが発生すると再計算トリガーが走る
Dim newValue As Double
newValue = CalculateComplexLogic(t) ' 外部関数でロジックを分離
If t.Text1 <> CStr(newValue) Then
t.Text1 = CStr(newValue)
End If
End If
End If
' オブジェクトの明示的解放(ループ内でのメモリリークを防止)
Set t = Nothing
Next i
CleanUp:
' 4. 徹底した後処理
LockWindowUpdate 0
projectApp.ScreenUpdating = True
' ガベージコレクションを促すため、参照を完全に切断
Set projectApp = Nothing
Debug.Print "Calculation Completed."
Exit Sub
ErrorHandler:
MsgBox "Critical Error: " & Err.Description, vbCritical
Resume CleanUp
End Sub
''' <summary>
''' 計算ロジックを分離し、Taskオブジェクトへのアクセスを最小限にする
''' </summary>
Private Function CalculateComplexLogic(ByRef t As Task) As Double
' ここに複雑な計算を記述。
' なるべくt.PropertyNameへのアクセス回数を減らし、変数に格納して計算せよ
Dim startD As Date: startD = t.Start
Dim finishD As Date: finishD = t.Finish
' 例: 期間に基づいた独自の重み付け計算
CalculateComplexLogic = DateDiff("d", startD, finishD) * 1.5
End Function
3. チーフアーキテクトが教える「深層の最適化」
上記のコードを単にコピーするだけでは不十分だ。真の最適化には、以下の3つの視点が必要である。
① COMオブジェクトのライフサイクル管理
VBAの `Set t = Nothing` は、単なる作法ではない。MS Projectのオブジェクトモデルは非常に重く、特に大規模プロジェクトでは、ループ内で参照を保持し続けるとメモリ断片化が発生し、次第に処理速度が低下する。
「使い終わったオブジェクトは即座に殺す」。これが鉄則だ。
② 書き込みトリガーの抑制(Dirty Flag戦略)
MS Projectにおいて最もコストが高い操作は、`Task`オブジェクトへのプロパティ書き込みだ。
`t.Text1 = “Value”` と記述した瞬間、Project内部では依存関係のチェックが走り、関連するサマリータスクの再計算が連鎖的に発生する。
これを防ぐには、「現在の値と比較し、異なる場合のみ書き込む」という単純なIF文が劇的な効果を発揮する。
③ Windows APIによるUIスレッドの解放
`Application.ScreenUpdating = False` は、Projectアプリケーション層での制御に過ぎない。OSレベルでウィンドウの再描画を停止させる `LockWindowUpdate` や `WM_SETREDRAW` を組み合わせることで、描画待ちによるCPUサイクルの浪費を完全に排除できる。
4. システム間連携への展開:レガシー環境の保守
もし、この計算結果を外部のERPやBIツールに連携させている場合、VBAでの計算後に `ActiveProject.Save` を呼ぶタイミングに注意せよ。
保存処理は全データの整合性チェックを伴うため、非常に重い。
「計算 $\rightarrow$ 保存 $\rightarrow$ 連携」ではなく、「計算 $\rightarrow$ 連携(メモリ上の値を直接送信) $\rightarrow$ 保存」 というフローに組み替えることで、ユーザーの待ち時間をさらに30%以上削減できる。
結論
大規模プロジェクトにおけるパフォーマンス最適化とは、機能を追加することではなく、「不要な動作をいかにして削ぎ落とすか」という引き算の美学である。
1. 数式を捨て、コードで制御せよ。
2. 書き込み回数を最小化せよ。
3. メモリとUIスレッドを支配せよ。
この原則を徹底したとき、あなたの構築したシステムは、単なる「動くツール」から「芸術的な高速システム」へと昇華する。
