【テクニカル・上級編】リソース割り当ての「優先順位」を自動設定するロジック – Project VBA解析バイブル

スポンサーリンク

Project VBAを掌握する極限の知見:リソース割り当て優先度自動化のアーキテクチャ

Microsoft Project(Project VBA)におけるリソース管理は、多くの開発者が途中で挫折する領域の一つである。GUIから行うリソース平準化(Leveling)は、一見するとスマートに見えるが、数千件規模のWBSを抱えるエンタープライズ環境においては、標準のアルゴリズムだけでは現場の政治的・実務的な優先順位を反映しきれない。

特に「リソースの過負荷(Over-allocation)」が発生した際、Projectがどのタスクを遅延させるかを制御する鍵となるのが、タスクの「優先順位(Priority)」プロパティ(値は0〜1000)である。この数値を動的に操作し、リソース平準化の挙動を完全に手掌中に収めるための自動化ロジックについて、オブジェクトのライフサイクル管理とパフォーマンスチューニングの極限から解説する。

—

1. 限界を超える:Project VBAにおけるオブジェクトモデルの罠

多くのVBAプログラマは、`ActiveProject.Tasks` や `Assignment` をループさせる際、次のような無邪気なコードを書く。

‘ 【アンチパターン】メモリリークとパフォーマンス低下を招くコード
Dim t As Task
For Each t In ActiveProject.Tasks
If Not t Is Nothing Then
t.Priority = 500
End If
Next t

このコードは小規模なプロジェクトであれば動くように見えるが、数千行のタスクと数万のリソース割り当てを持つプロジェクトファイル(.mpp)では、COMインターフェイスのマーシャリングオーバーヘッドと参照カウントの肥大化により、ガベージコレクションが追いつかなくなる。

極限のパフォーマンスが求められる現場では、「オブジェクトの明示的な解放」「遅延バインディングの排除」「レンジ操作の最小化」が絶対の鉄則となる。

—

2. 実装アーキテクチャ:優先順位自動設定エンジン

以下に提示するのは、ビジネスロジック(タスクの重要度、期限、コスト影響度)を解析し、一括してリソース割り当ての優先順位を書き換える実用的なVBAモジュールである。

このコードでは、ComObjのメモリリークを防ぐための厳格な参照解放と、実行速度を限界まで高めるための画面描画停止(`ScreenUpdating`)を実装している。

Option Explicit

‘ Windows API: 処理速度計測用(高精度タイマー)
If VBA7 Then
Private Declare PtrSafe Function QueryPerformanceCounter Lib “kernel32” (ByRef lpPerformanceCount As Currency) As Long
Private Declare PtrSafe Function QueryPerformanceFrequency Lib “kernel32” (ByRef lpFrequency As Currency) As Long
Else
Private Declare Function QueryPerformanceCounter Lib “kernel32” (ByRef lpPerformanceCount As Currency) As Long
Private Declare Function QueryPerformanceFrequency Lib “kernel32” (ByRef lpFrequency As Currency) As Long
End If

Public Sub ExecutePriorityLevelingEngine()
Dim startTime As Currency, freq As Currency
QueryPerformanceFrequency freq
QueryPerformanceCounter startTime

‘ 1. パフォーマンス・最適化の極限:描画と警告の完全抑制
Dim origScreenUpdating As Boolean
origScreenUpdating = Not Application.ScreenUpdating

Application.ScreenUpdating = False
Application.Calculation = pjManual ‘ 計算モードを手動に切り替え

On Error GoTo ErrorHandler

Dim proj As Project
Set proj = ActiveProject

Dim t As Task
Dim assignmentCount As Long
assignmentCount = 0

‘ 2. トランザクション処理の開始(Undoスタックの肥大化を防ぐため必要最小限に)
App.UndoCheckPoint

Dim targetTask As Task
For Each targetTask In proj.Tasks
If Not targetTask Is Nothing Then
‘ サマリータスクやマイルストーンを除外せず、ビジネスロジックで重み付け
If Not targetTask.Summary And targetTask.Milestone = False Then

‘ ロジック例:
‘ 期限(Finish)が迫っている、かつ、コスト(Cost)が高いタスクほど優先度を「999」に近づける
Dim calculatedPriority As Integer
calculatedPriority = CalculateDynamicPriority(targetTask)

‘ 優先度の書き込み
targetTask.Priority = calculatedPriority

‘ リソース割り当て(Assignment)側の優先度やコスト調整が必要な場合のフック
Dim assn As Assignment
For Each assn In targetTask.Assignments
‘ 例:特定のリソース(高単価リソース)がアサインされている場合、さらに優先度をブースト
If assn.Resource.StandardRate > 5000 Then
targetTask.Priority = 1000 ‘ 絶対死守タスク
End If
Set assn = Nothing ‘ ループ内オブジェクトの即時解放
Next assn

assignmentCount = assignmentCount + 1
End If
End If
Set targetTask = Nothing
Next targetTask

‘ 3. 再計算と平準化のトリガー
Application.Calculation = pjAutomatic
proj.Calculate

‘ ログ出力とパフォーマンス計測
Dim endTime As Currency, elapsed As Double
QueryPerformanceCounter endTime
QueryPerformanceFrequency freq
elapsed = (endTime – startTime) / freq

MsgBox “優先順位の自動割り当てが完了しました。” & vbCrLf & _
“処理対象タスク数: ” & assignmentCount & vbCrLf & _
“処理時間: ” & Format(elapsed, “0.000”) & ” 秒”, vbInformation, “チーフアーキテクト監視システム”

CleanUp:
‘ 状態の復元
Application.ScreenUpdating = True
Application.Calculation = pjAutomatic
Exit Sub

ErrorHandler:
MsgBox “致命的なエラーが発生しました [” & Err.Number & “]: ” & Err.Description, vbCritical, “System Error”
Resume CleanUp
End Sub

‘ ビジネスロジック関数:タスクの属性から動的に優先度を算出
Private Function CalculateDynamicPriority(ByRef t As Task) As Integer
Dim basePriority As Integer
basePriority = 500 ‘ デフォルト

‘ 例外処理:期限切れ間近のタスクは優先度を最大化
If t.Finish < Now + 7 Then basePriority = 900 ElseIf t.Cost > 1000000 Then
‘ 高コストタスクの重み付け
basePriority = 750
Else
‘ 通常の進捗や重要度(WBSの階層やTextフィールドなど)に基づく演算
basePriority = 400 + (t.OutlineNumber 10)
If basePriority > 999 Then basePriority = 999
If basePriority < 0 Then basePriority = 0 End If CalculateDynamicPriority = basePriority End Function ---

3. レガシー環境とAPI連携における知見

エンタープライズ環境では、MS Project単体でデータが完結することは稀である。基幹システム(SAPや独自ERP)からREST API経由で取得した最新の「リソース負荷係数」や「コスト制約データ」を、VBA経由でProjectに流し込む必要がある。

ここで問題になるのが、COM相互運用性(COM Interop)におけるメモリリークである。特に `For Each` を多用したコードや、Variant型でのオブジェクト保持は、VBAランタイムのヒープ領域を圧迫し、大規模プロジェクトのオープン時に「メモリ不足」エラーを引き起こす原因となる。

メモリ最適化の極意:

1. オブジェクト変数への明示的代入と `Nothing` の徹底
ループ内で生成・取得したオブジェクト(`Task`, `Resource`, `Assignment`)は、ループのイテレーションごとに `Set obj = Nothing` を実行し、参照カウントを即座にデクリメントさせなければならない。
2. エラーハンドリングでの確実にステートを戻す設計
`ScreenUpdating = False` や `Calculation = pjManual` を行った状態でエラー落ちすると、ProjectのGUIが操作不能なフリーズ状態に陥る。必ず `On Error GoTo` を経由して元のステートを復元する防壁を張ること。

—

4. チーフアーキテクトからの提言

リソース平準化の自動化は、単にコードを書けば動くというものではない。「どのタスクを犠牲にし、どのタスクを死守するか」という企業の意思決定ロジックを、VBAという強靭かつ泥臭いインターフェイスを通じて正確にコードへ落とし込む作業に他ならない。

今回解説したアーキテクチャをベースに、自社のプロジェクト管理ポリシーに最適化されたカスタムロジックを構築してほしい。真の自動化エンジニアであれば、コードの美しさと、ハードウェアの限界を見据えた堅牢性を両立させることだ。

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