Project VBAの深淵:CSV依存関係をUniqueIDで制圧する「高速マッピング」の技術
Project VBA(Microsoft Projectのオブジェクトモデル)を扱う際、多くのエンジニアが陥る罠がある。それは、`Task`オブジェクトに対する安易なループ処理だ。特に数千行規模のWBSにおいて、依存関係(Predecessors)を個別に設定しようとすれば、Projectの再計算エンジンが暴走し、処理時間は指数関数的に増大する。
本稿では、レガシーなProject環境においても、CSVから読み込んだ依存関係をUniqueIDをキーとして爆速で解決する、プロフェッショナルなマッピング手法を伝授する。
—
1. なぜ「UniqueID」を用いるべきなのか
MS Projectには「ID」と「UniqueID」の二つが存在する。IDはWBSの行番号に依存する可変値であり、削除や挿入で容易に崩壊する。対してUniqueIDは、タスクが生成された瞬間に割り振られ、そのプロジェクト内で永続化される唯一の識別子だ。
外部システムとの連携においてIDをキーにするのは、砂上に城を築くようなもの。必ずUniqueIDをマッピングの軸とし、`Scripting.Dictionary`を用いてメモリ上で高速検索を行うべきである。
2. 実装の要諦:Dictionaryによるメモリ・インデックス
数千のタスクを毎回全件スキャンしてはならない。一度だけ全タスクを走査し、`UniqueID`をキー、`Task`オブジェクトを値としてDictionaryにキャッシュする。これが「計算量O(1)」を実現する唯一の道だ。
Option Explicit
‘ 伝説のアーキテクトが推奨する、メモリ効率を考慮したマッピングロジック
Public Sub LinkTasksFromCSV(ByVal csvPath As String)
Dim proj As Project
Dim tsk As Task
Dim taskMap As Object ‘ Scripting.Dictionary
Set taskMap = CreateObject(“Scripting.Dictionary”)
Set proj = ActiveProject
‘ 1. インデックスの構築(O(n))
‘ これにより、検索コストを最小化する
For Each tsk In proj.Tasks
If Not tsk Is Nothing Then
If Not taskMap.Exists(tsk.UniqueID) Then
taskMap.Add tsk.UniqueID, tsk
End If
End If
Next tsk
‘ 2. CSV読み込みと依存関係の設定(O(m))
‘ ファイルI/Oは極力ストリームで行うのが定石
Call ProcessCSVAndLink(csvPath, taskMap)
‘ 3. メモリの明示的解放
‘ VBAはガベージコレクションが脆弱なため、参照解除は義務である
taskMap.RemoveAll
Set taskMap = Nothing
End Sub
3. レガシー環境におけるパフォーマンスの最適化
オブジェクトの明示的解放
VBAはCOMベースであり、循環参照や巨大なDictionaryオブジェクトの放置は、メモリリークの温床となる。`Set obj = Nothing` を怠ることは、大規模開発においては「罪」である。
再計算の抑制
依存関係を一つ設定するたびにProjectが再計算を行うと、UIがフリーズする。可能であれば `Application.Calculation = pjManual` を一時的に設定し、一括反映後に `Calculation = pjAutomatic` に戻す戦略をとれ。
システム間連携のTips
もしCSVが巨大で、VBAの `Line Input` ではメモリが不足する場合は、Windows APIの `CreateFileMapping` を検討せよ。メモリマップドファイルを使用することで、物理的な読み込み速度を極限まで引き上げることが可能だ。
4. 実践的コード:依存関係の適用
以下は、Dictionaryを介してCSVから読み込んだ「UniqueID」ベースの先行タスクを設定する核心部分だ。
Private Sub ProcessCSVAndLink(ByVal path As String, ByRef map As Object)
Dim fileNo As Integer
Dim line As String
Dim parts() As String
fileNo = FreeFile
Open path For Input As #fileNo
‘ ヘッダー読み飛ばし等の制御が必要
Line Input #fileNo, line
Do While Not EOF(fileNo)
Line Input #fileNo, line
parts = Split(line, “,”) ‘ CSV: TaskUniqueID, PredecessorUniqueID
Dim targetID As Long: targetID = CLng(parts(0))
Dim predID As Long: predID = CLng(parts(1))
‘ Dictionaryから即座にオブジェクトを特定
If map.Exists(targetID) And map.Exists(predID) Then
Dim tsk As Task
Set tsk = map(targetID)
‘ 依存関係の設定(FS:終了-開始関係を想定)
‘ PredecessorsプロパティにUniqueIDを渡すのがポイント
tsk.Predecessors = map(predID).UniqueID
End If
Loop
Close #fileNo
End Sub
—
チーフアーキテクトの戒め
システム連携において最も重要なのは「美しさ」ではない。「堅牢性」だ。
タスクが削除されている可能性、CSV内のIDが欠損している可能性、あるいは依存関係が循環している可能性。これらをエラーハンドリング(`On Error Resume Next` の乱用は避け、個別にチェックせよ)で徹底的に排除した者だけが、Project VBAを真に掌握できる。
コードは、書いた本人以外の人間が保守する時に初めて完成する。変数の命名、コメントの意図、そしてメモリ管理。これらの細部が、君のシステムの寿命を決定づける。
健闘を祈る。
