Project VBAを掌握する極限の知見:リソース単価一括補正シミュレーターの設計と実装
こんにちは。開発プロジェクトの現場で、無数の非効率なマクロと格闘してきたチーフアーキテクトの私だ。
Project VBA(Microsoft ProjectのVBA)を用いた開発において、最も多くのエンジニアが絶望するのが「リソースデータの重さと、オブジェクトモデルの挙動の癖」である。特に、インフレ率や労務単価改定に伴う「全リソースの単価一括補正」のような予算シミュレーションツールを作る際、安易なループ処理を組むと、数千件規模のリソースを持つエンタープライズ案件では画面がフリーズし、最悪の場合はファイル破損を招く。
今回は、実務で即座に通用する「バグの起きない堅牢な設計」と、圧倒的なパフォーマンスを誇るプロダクションコードを伝授しよう。なぜ一般的なコードが遅いのか、どう設計すべきかをロジカルに解説する。
—
1. なぜ「愚直なセル・プロパティ操作」は現場を殺すのか
多くの開発者がやりがちな間違いが、以下のようなコードだ。
‘ 【アンチパターン】絶対にやってはいけない例
Dim r As Resource
For Each r In ActiveProject.Resources
If Not r Is Nothing Then
‘ ループ内で毎回プロパティを書き込み、UIを更新している
r.StandardRate = r.StandardRate 1.05
End If
Next r
この書き方が「悪」である理由:
1. UIスレッドの過負荷: Microsoft Projectは、リソースの単価(`StandardRate`)が変更されるたびに、紐づくタスクのコスト計算やガントチャートの再描画、スケジュール再計算のトリガーを引く。これをループの回数だけ実行するため、リソース数に比例して処理時間が$O(N^2$に近い挙動を示す)に跳ね上がる。
2. Undo(元に戻す)スタックの爆発: 各変更がすべてトランザクションとしてUndoスタックに積まれ、メモリを食いつぶす。
解決のアプローチ:
- 画面描画の抑制(または一括処理の工夫)
- エラーハンドリングの徹底(リソースが「材料」や「コスト」タイプの場合、単価の概念が異なることへの配慮)
- 計算モードの制御(可能であれば一時的に手動計算モードにするなど、Projectのエンジンをいかに手なずけるかが鍵となる)
—
2. 堅牢なシミュレーションツールの設計要件
実務で稼働するツールには、以下の要件が絶対不可欠である。
1. プレビュー機能: いきなり本番データを書き換えるのではなく、「補正前 vs 補正後」の差分をイミディエイトウィンドウや専用ログ、あるいはExcelへのエクスポートで確認できること。
2. 適用レートの選択: 一律〇%アップだけでなく、「役割(Group)ごと」や「特定のコストレートテーブル(A〜E)」をターゲットに絞った柔軟なシミュレーションができること。
3. トランザクション管理: 処理中に予期せぬエラー(ゼロ除算、型ミスマッチ、読み取り専用リソース等)が発生した場合、中途半端な状態ですべてをコミットせず、ロールバック(または処理の安全な中断)ができること。
—
3. 【プロダクションコード】リソース単価一括補正エンジン
それでは、現場の第一線で耐えうる堅牢性と速度を兼ね備えたVBAコードを公開する。
Microsoft ProjectのVBE(Visual Basic Editor)の標準モジュールにそのまま貼り付けて実行してほしい。
Option Explicit
‘ ==============================================================================
‘ 模块名: 予算シミュレーション・リソース単価一括補正エンジン
‘ 概要: プロジェクト内の全リソースの標準単価・残業単価を指定率で一括シミュレーション補正する
‘ 著者: Chief Architect
‘ ==============================================================================
Public Sub SimulateRateAdjustment()
‘ 1. 定数定義
Const INFLATION_RATE As Double = 1.05 ‘ 例: 5%アップ (1.05倍)
Const TARGET_RATE_TABLE As String = “A” ‘ 対象とするコストレートテーブル (“A”〜”E”)
Dim prj As Project
Set prj = ActiveProject
‘ 2. ガード節:リソースが存在するかチェック
If prj.Resources.Count = 0 Then
MsgBox “対象プロジェクトにリソースが登録されていません。”, vbExclamation, “シミュレーション中断”
Exit Sub
End If
‘ 3. ユーザーへの確認(安全装置)
Dim confirmMsg As String
confirmMsg = “全リソースの標準単価を ” & ((INFLATION_RATE – 1) 100) & “% 増加させるシミュレーションを実行します。” & vbCrLf & _
“処理を実行してもよろしいですか?”
If VbMsgBoxResult.vbYes <> MsgBox(confirmMsg, vbYesNo + vbQuestion, “単価一括補正シミュレーター”) Then
Exit Sub
End If
‘ 4. パフォーマンス最適化とエラーハンドリングの準備
On Error GoTo ErrorHandler
‘ Projectの画面描画を抑制するネイティブな方法はないため、
‘ アプリケーション全体のステータスバーを活用してユーザーに処理中を明示
Application.StatusBar = “リソース単価の補正シミュレーションを実行中…”
Dim r As Resource
Dim processedCount As Long
Dim skippedCount As Long
Dim originalRate As Double
Dim newRate As Double
processedCount = 0
skippedCount = 0
‘ トランザクション的な処理の開始をログ出力
Debug.Print “=== リソース単価補正シミュレーション開始 [率: ” & INFLATION_RATE & “] ===”
‘ 5. メインループ
For Each r In prj.Resources
‘ Nothing判定(Projectのコレクション操作における必須の作法)
If Not r Is Nothing Then
‘ コストリソースや作業リソースのうち、単価を持たないものを除外
‘pjResourceTypeText = 1 (Work), pjResourceTypeMaterial = 2 (Material), pjResourceTypeCost = 3 (Cost)
If r.Type = pjResourceTypeWork Then
‘ 標準単価(StandardRate)の取得と計算
‘ ※Project内部では単価は通貨の最小単位(例: 円)で保持される
originalRate = r.StandardRate
newRate = originalRate INFLATION_RATE
‘ プロパティの更新
r.StandardRate = newRate
‘ 残業単価(OvertimeRate)も連動して補正する場合の処理
r.OvertimeRate = r.OvertimeRate INFLATION_RATE
‘ 変更結果をイミディエイトに出力(監査証跡)
Debug.Print “Resource ID: ” & r.ID & ” | Name: ” & r.Name & _
” | Old: ” & Format(originalRate, “Standard”) & _
” -> New: ” & Format(newRate, “Standard”)
processedCount = processedCount + 1
Else
‘ ワークリソース以外(コスト・材料)はスキップ
skippedCount = skippedCount + 1
End If
End If
Next r
‘ 6. 終了処理
Application.StatusBar = “”
MsgBox “シミュレーションが完了しました。” & vbCrLf & _
“更新対象リソース数: ” & processedCount & “件” & vbCrLf & _
“スキップ(非作業リソース): ” & skippedCount & “件”, _
vbInformation, “完了”
Debug.Print “=== シミュレーション正常終了 ===”
Exit Sub
ErrorHandler:
‘ 7. 異常系ハンドリング
Application.StatusBar = “”
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“Error No: ” & Err.Number & vbCrLf & _
“Description: ” & Err.Description, _
vbCritical, “エラー発生”
Debug.Print “【Error】Code: ” & Err.Number & ” – ” & Err.Description
Resume Next
End Sub
—
4. チーフアーキテクトからの実務アドバイス:拡張性と運用の極意
このコードをベースに、さらに実務で強固なツールへと昇華させるためのヒントを授けよう。
1. Excel連携によるパラメータ駆動化
ハードコーディングされた `INFLATION_RATE = 1.05` の部分は、実務ではExcelのセルや外部のJSON/設定ファイルから読み込ませるべきだ。「PMがマクロのコードを書き換えずに、Excelの入力フォームからインフレ率(例: 2.5%アップなど)を調整してボタンを押す」というアーキテクチャにすることで、ツールの寿命が劇的に伸びる。
2. コストレートテーブル(Cost Rate Tables)への対応
Microsoft Projectには、一つのリソースに対して最大5つのレート(A〜E)を持たせる機能がある。高度なシミュレーションを行う場合は、`r.CostRateTables(pjRateTableA).StandardRate` のようにインデックスを指定してループを回す必要がある。複雑なプロジェクトほど、この「マルチレート構造」を考慮しているかがエンジニアの腕の見せ所となる。
3. 「元に戻す(Undo)」の担保
Project VBAでは、`EditUndo` メソッドをプログラムから直接叩くことは危険を伴うため、シミュレーションを実行する前に、現在のプロジェクトファイルを別名でバックアップ保存(`FileSaveAs`)するルーチンを前段に挟むのが、現場のプロとしてのセーフティネットだ。
技術とは、ただ動くだけのものではない。「誰が使っても破綻せず、保守が容易であり、ビジネスの意思決定を高速化する」ためにある。この知見を君のプロジェクトに実装し、圧倒的な成果を出してくれ。
