【テクニカル・上級編】リソースの割り当てを「期間」でフィルタリングして一括編集する – Project VBA解析バイブル

スポンサーリンク

Project VBAを掌握する極限の知見

第4章:リソース管理の暗黒領域 — 期間フィルタリングによる一括処理の極意

Microsoft ProjectのVBA(Project VBA)を用いた大規模リソース管理において、最も開発者を絶望させるのは「時間の経過と共に肥大化したアサインメント(割り当て)データの迷宮」である。

数千件のタスクと数百人のリソースが絡み合うEnterprise環境において、特定の期間(例:度重なる仕様変更によりリスケジュールされたQ3の特定スプリント)に稼働しているリソースの単価変更や、不要なアサインメントのパージを愚直なループ処理で行えば、ProjectのGUIはフリーズし、最悪の場合はCOMコンポーネントの応答停止(RPCサーバーの不応答)を引き起こす。

本稿では、レガシーなProjectオブジェクトモデルの制約を突破し、メモリフットプリントを最小限に抑えながら、「期間フィルタリング」によるリソース割り当ての一括編集を極限のパフォーマンスで実現するアーキテクチャを解説する。

—

1. Project VBAにおけるパフォーマンス劣化の根本原因

なぜ標準的な `For Each` ループによるアサインメント走査は遅いのか。
それは、VBAとMS ProjectのCOM境界(COM Interop)を跨ぐラウンドトリップのコストが、想像以上に重いためである。タスクコレクション、リソースコレクション、そしてその交差点にあるアサインメントコレクションを多重ループで回すと、数万回のCOM呼び出しが発生し、CPUキャッシュとVBAのバリアント解決機構を完全に破壊する。

このボトルネックを回避するためのアプローチは以下の3点に集約される。

1. 暗黙のオブジェクト参照の排除: 変数スコープを厳密に定義し、COMポインタの解放漏れを防ぐ。
2. 高速な期間判定ロジック: 日付比較におけるVariant型のオーバーヘッドを排除し、Long型(内部シリアル値)または `Date` 型での厳密な境界値判定を行う。
3. トランザクション的処理: 画面描画(UIの再描画)を完全に抑制し、Undoスタックの肥大化を防ぐ。

—

2. 実装コード:期間フィルタリングによるリソースアサインメント一括処理

以下のコードは、指定した期間(Start〜Finish)にオーバーラップするアサインメントを高速に抽出し、コスト(単価または残余コスト)の一括調整と、条件に合致する割り当ての削除を行う実用的なプロシージャである。

Option Explicit

‘ ==============================================================================
‘ 処理名: BulkUpdateResourceAssignmentsByPeriod
‘ 概要: 指定された期間に該当するリソース割り当てを抽出し、コストを一括変更または解除する
‘ アーキテクト特記事項:
‘ – 画面描画を完全に抑制し、COMコンポーネントへの往復コストを極限まで削減。
‘ – オブジェクトのライフサイクルを明示的に管理し、メモリリークを防止。
‘ ==============================================================================
Public Sub BulkUpdateResourceAssignmentsByPeriod( _
ByVal targetStart As Date, _
ByVal targetFinish As Date, _
ByVal newCostRate As Double, _
Optional ByVal removeUnused As Boolean = False)

‘ パフォーマンスとメモリ保護のための宣言
Dim t As Task
Dim assn As Assignment
Dim r As Resource

‘ エラーハンドリングとUIロック解除の担保
On Error GoTo ErrorHandler

‘ 【極限最適化】画面描画と自動計算を停止し、COM呼び出しのオーバーヘッドを消去
App.ScreenUpdating False
CalcMode pjCalcManual

Debug.Print “[INFO] 処理開始: 期間 ” & targetStart & ” ~ ” & targetFinish

‘ プロジェクト内の全タスクを走査
For Each t In ActiveProject.Tasks
‘ 冗長なタスク(サマリタスクや空行)のスキップ
If Not t Is Nothing Then
If Not t.Summary And t.Active Then

‘ タスクに紐付くアサインメントコレクションを取得
For Each assn In t.Assignments
If Not assn Is Nothing Then

‘ 【期間フィルタリングの核心】
‘ 割り当て期間がターゲット期間と交差(オーバーラップ)しているか判定
‘ 条件: (Assignment.Start <= TargetFinish) And (Assignment.Finish >= TargetStart)
If (assn.Start <= targetFinish) And (assn.Finish >= targetStart) Then

‘ デバッグ出力(必要に応じてコメントアウト)
‘ Debug.Print “対象検出 – タスク: ” & t.Name & ” / リソース: ” & assn.ResourceName

If removeUnused Then
‘ 条件に合致する場合、割り当て自体を削除(リソースの解放)
assn.Delete
Else
‘ コストまたは単価の動的変更
‘ ※Enterpriseリソースの場合、基底単価の変更は権限が必要なため、
‘ ここではアサインメント単位のコスト(Cost)またはオーバーライド単価を操作
assn.Cost = assn.Cost (1 + newCostRate)
End If

End If

End If

‘ COMオブジェクトの参照を即時解放(メモリ最適化)
Set assn = Nothing
Next assn

End If
End If

‘ タスクオブジェクトの参照解放
Set t = Nothing
Next t

CleanUp:
‘ 【重要】処理終了後は必ずUIと計算モードを復元する
App.ScreenUpdating True
CalcMode pjCalcAutomatic

‘ 再計算を実行
CalculateAll
Debug.Print “[INFO] 処理完了: 正常終了”
Exit Sub

ErrorHandler:
‘ 異常発生時のフェイルセーフ
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “Project VBA Core Engine”
Resume CleanUp
End Sub

—

3. チーフアーキテクトによる深層解説

A. メモリマネジメントとCOM解放のイディオム

VBAのガベージコレクションは参照カウンタ方式(Reference Counting)に依存している。`For Each` ループ内で `Set assn = Nothing` を明示的に呼び出しているのは、Project側(C++製COMサーバー)が保持するアサインメントのインターフェースポインタを早期に解放するためである。これを怠ると、数万件のループ中にVBAのヒープ領域が肥大化し、最悪の場合は `OutOfMemory` エラーを引き起こす。

B. 期間交差判定(Overlap Logic)の数学的正確性

日付の重複判定において、初心者が陥りがちなミスは「完全一致」や「内包判定」のみを実装することである。実務上のリソース管理では、「部分的な重なり(Partial Overlap)」を正確に捉えなければならない。
本コードで採用している条件式:
$$\text{Assn.Start} \le \text{Target.Finish} \quad \land \quad \text{Assn.Finish} \ge \text{Target.Start}$$
これは、1点でも期間が重複するすべてのケース(タスクが期間を跨ぐ場合、期間内に収まる場合を含む)を漏れなく捕捉する、数学的に最も堅牢な判定ロジックである。

C. レガシー環境・エンタープライズ連携における注意点

Project Server / Project Online(Project Web App: PWA)環境において、Enterprise リソースの単価やコストをVBAから直接書き換える場合、チェックアウト(Check-out)状態の制約に直面する。
もし対象のリソースが他のユーザーによってロックされている場合、`assn.Cost` などの書き込みプロパティはランタイムエラー(トラップ可能なエラー 1101 等)をスローする。
堅牢なシステム間連携ツールとして昇華させるためには、上記のエラーハンドリング部分に `Err.Number` を監視するロジックを組み込み、失敗したアサインメントIDをログテーブル(あるいはExcelワークシート)へ退避させるリトライ機構を実装すべきである。

—

結言

VBAは、しばしば「時代遅れのスク言語」と揶揄される。しかし、それは言語の限界ではなく、それを扱うエンジニアがオブジェクトのライフサイクルとCOMの挙動を理解していない言い訳に過ぎない。
ここに示した極限の知見を武器に、あなたのプロジェクト管理基盤を、真にスケーラブルなエンタープライズ・アーキテクチャへと昇華させてほしい。

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