Project VBAの深淵:Assignment.ActualWorkを破壊せずにリソースを置換する「不変の作法」
Project VBA(MS Project Object Model)を触る際、多くのエンジニアが陥る罠がある。それは「リソースの置換」を単なる`Assignment`オブジェクトの付け替えだと甘く見ることだ。
リソースを入れ替えた瞬間に過去の実績工数(`ActualWork`)が計算ロジックによって吹き飛んだり、あるいは整合性の取れない「ゴースト工数」が残存したりする。これはProjectのスケジュールエンジンが、割り当て変更時に行う再計算プロセスの副作用だ。
本稿では、レガシーな巨大プロジェクトファイルを破壊せず、実績という名の「資産」を確実に次代へ引き継ぐための、メモリ効率と安全性を極めた置換アルゴリズムを伝授する。
—
1. 破壊のメカニズム:なぜ置換で実績が消失するのか
Projectの内部モデルにおいて、`Assignment`はタスクとリソースを結ぶ「橋」に過ぎない。しかし、この橋を架け替えるとき、MS Projectのスケジュールエンジンは、過去の実績値と残存工数(`RemainingWork`)を再評価する。
特に、`ActualWork`が記録された状態で`Assignment`を削除(Delete)すると、紐付いていた実績値の親テーブルへの集計が不安定になるケースがある。これを避けるには、「直接的な削除」ではなく「置換による状態更新」という手順を厳守しなければならない。
2. 実装の極意:安全なリソース置換アルゴリズム
以下のコードは、単なるプロパティ操作ではない。メモリ解放とエラーハンドリングを考慮した、極めて堅牢な置換ロジックだ。
‘ @brief 特定のタスクにおけるリソース置換の安全な実装
‘ @param tsk Target Task
‘ @param oldRes Target Resource to be replaced
‘ @param newRes New Resource to assign
Public Sub SafeResourceSwap(ByRef tsk As Task, ByRef oldRes As Resource, ByRef newRes As Resource)
Dim asn As Assignment
Dim actualWork As Variant
Dim remainingWork As Variant
On Error GoTo ErrorHandler
‘ 1. 対象アサインメントの特定とデータ退避
Set asn = tsk.Assignments.Item(oldRes.Name)
‘ 実績を退避させる(ActualWorkは分単位のVariantとして扱うのが鉄則)
actualWork = asn.ActualWork
remainingWork = asn.RemainingWork
‘ 2. メモリ管理: 不要なオブジェクトの明示的解放はVBAの基本だが、
‘ Project Object ModelではSet Nothingのタイミングが重要
‘ 3. 新規アサインの実行
‘ ここで直接置換せず、一度新規追加してから旧を消すことで計算エンジンを安定させる
Dim newAsn As Assignment
Set newAsn = tsk.Assignments.Add(ResourceID:=newRes.ID)
‘ 4. 実績の転記(ここで整合性を強制的に書き込む)
newAsn.ActualWork = actualWork
newAsn.RemainingWork = remainingWork
‘ 5. 旧アサインの破棄
asn.Delete
‘ オブジェクトの明示的解放(GCを待たないプロの作法)
Set newAsn = Nothing
Set asn = Nothing
Exit Sub
ErrorHandler:
‘ Win32 APIでエラーログをシステムイベントへ飛ばす等の実装を推奨
Debug.Print “Critical Error: ” & Err.Description
Err.Clear
End Sub
3. シニアエンジニアが守るべき3つの鉄則
① 浮動小数点の罠を回避せよ
Project VBAの工数は内部的に「分」単位で保持されている。`ActualWork`を扱う際、型変換を伴う演算を行うと微細な誤差が蓄積する。必ず`Variant`型で保持し、代入時にProjectの暗黙的な型変換に委ねるのが最も安全だ。
② メモリリークとオブジェクト循環参照の排除
大規模なMPPファイルに対して数千件の置換処理を行う場合、`Set`した変数を適切に`Nothing`化しないと、ProjectのCOMインターフェースがメモリを解放できず、動作が徐々に重くなる。ループ処理内でのオブジェクト生成は、イテレーション終了時に必ず解放すること。
③ システム間連携での「同期待ち」
もし貴殿が外部のERPや工数管理システムと連携させているなら、`Application.CalculateAll`を無闇に叩いてはならない。リソース置換は「重い」処理だ。バッチ処理の最後に一度だけ計算を走らせるのが、トータルパフォーマンスを向上させる唯一の道である。
—
結びに:レガシーシステムと向き合うということ
Project VBAは、一見すると古い技術に見えるかもしれない。しかし、その内部で動くオブジェクトモデルは、数十年のプロジェクト管理の知見が詰まった「巨大な計算資源」だ。
実績工数という名の「過去」を切り捨てずに、新しいリソースという「未来」を接続する。この作業は、単なるコード書きではなく、プロジェクトの歴史を継承する儀式に近い。
コードは嘘をつかない。だが、仕様の隙間には常に罠がある。賢明なエンジニア諸君は、常に計算エンジンの挙動を疑い、メモリの深淵を覗き込みながら、確実なアーキテクチャを構築してほしい。
「自動化とは、人間の意志を機械に正確に移植する行為である」
この言葉を忘れないでいただきたい。
