Project VBAを掌握する:制約条件インポートの最適化とメモリ管理の深淵
多くのエンジニアが「動けばいい」と妥協するProject VBAの制約条件設定。だが、数千行規模のスケジュールを扱う大規模プロジェクトにおいて、場当たり的なマッピングはメモリリークとパフォーマンス低下を招く致命的な地雷となる。
今日は、外部ExcelデータからProjectの制約条件を「いかにエレガントに、かつ堅牢に」流し込むか、その極限のアーキテクチャを伝授する。
—
1. マッピングロジックの「静的化」によるオーバーヘッド削減
制約条件(ConstraintType)の変換において、`Select Case`や`If`文をループ内で乱用するのは愚の骨頂だ。VBAのインタープリタは分岐命令を嫌う。
ここで採るべきは、Dictionaryオブジェクトを用いた定数マッピングのキャッシュ戦略である。一度生成したマップをメモリに固定し、再利用することで、探索コストをO(1)に抑える。
‘ 制約条件マッピングクラス:シングルトンに近い振る舞いでメモリを最適化
Private pConstraintMap As Object
Public Property Get ConstraintMap() As Object
If pConstraintMap Is Nothing Then
Set pConstraintMap = CreateObject(“Scripting.Dictionary”)
‘ プロジェクト定数と文字列の静的バインディング
With pConstraintMap
.Add “ASAP”, 0 ‘ pjConstraintAsSoonAsPossible
.Add “ALAP”, 1 ‘ pjConstraintAsLateAsPossible
.Add “SNET”, 2 ‘ pjConstraintStartNoEarlierThan
.Add “SNLT”, 3 ‘ pjConstraintStartNoLaterThan
.Add “FNET”, 4 ‘ pjConstraintFinishNoEarlierThan
.Add “FNLT”, 5 ‘ pjConstraintFinishNoLaterThan
.Add “MSO”, 6 ‘ pjConstraintMustStartOn
.Add “MFO”, 7 ‘ pjConstraintMustFinishOn
End With
End If
Set ConstraintMap = pConstraintMap
End Property
—
2. オブジェクトのライフサイクルと解放の鉄則
VBAにおいて、`Task`オブジェクトをループ内で逐次取得する実装は、参照カウントの増大によるGC(ガベージコレクション)の遅延を招く。特にProjectオブジェクトモデルはCOM層を介すため、解放の漏れが即座にメモリ肥大化に直結する。
究極のループ処理パターン
`Set`したオブジェクトは、必ずループのスコープ内で明示的に`Nothing`を代入し、参照カウントを即座に解放せよ。
Public Sub ApplyConstraints(ByRef proj As MSProject.Project, ByRef rawData As Variant)
Dim tsk As MSProject.Task
Dim i As Long
Dim constraintKey As String
‘ 画面更新停止でパフォーマンスを極限まで引き上げる
Application.ScreenUpdating = False
On Error Resume Next ‘ インポート中の例外をハンドリング
For i = LBound(rawData, 1) To UBound(rawData, 1)
Set tsk = proj.Tasks(rawData(i, 1)) ‘ ID指定でタスクを取得
If Not tsk Is Nothing Then
constraintKey = UCase(rawData(i, 2))
If ConstraintMap.Exists(constraintKey) Then
tsk.ConstraintType = ConstraintMap(constraintKey)
End If
End If
‘ オブジェクトの明示的解放:これがなければ数万行で死ぬ
Set tsk = Nothing
Next i
Application.ScreenUpdating = True
End Sub
—
3. レガシー環境における「Windows API」の活用
大規模なインポート処理を行う際、ExcelとProject間でクリップボードを経由させたり、非同期通信を強いるのは危険だ。システム間連携の安定性を高めるには、`CoInitialize` / `CoUninitialize` を呼び出し、COMスレッドを明示的に制御する手法があるが、VBA単体であれば「処理のバッチ化」が最も効果的な最適化となる。
もし、処理中に「応答なし」を防ぎたいのであれば、`DoEvents`を安易に使うのではなく、以下のAPIで進捗をコントロールせよ。
‘ 処理のフリーズを回避するための最小限のAPI利用
If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
Private Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
End If
—
4. アーキテクトからの提言:制約条件の「完全性」
制約条件を一括インポートする際、最も陥りやすい罠は「日付(ConstraintDate)の不整合」である。`ConstraintType`を適用した直後に`ConstraintDate`を設定しようとすると、Projectのスケジュールエンジンが再計算(Re-calculate)を走り、処理速度を劇的に低下させる。
極限の最適化テクニック:
1. 計算モードを手動に切り替える: `Application.Calculation = pjManual`
2. 制約タイプを流し込む
3. 制約日付を流し込む
4. 最後に計算を実行する: `Application.CalculateAll`
この手順を踏むだけで、インポート速度は数倍から数十倍に跳ね上がる。スケジュールエンジンを無駄に走らせないことこそ、大規模システム構築における正義である。
—
最後に:コードは「対話」である
VBAはレガシーと言われるが、ProjectのCOMモデルを操る上でこれほど強力なインターフェースはない。技術の本質は、言語の古さではなく、その背後にあるメモリ管理とオブジェクトのライフサイクルをいかに支配するかに集約される。
今日のこの知見が、貴殿のプロジェクトにおける「停止しないインポートエンジン」の礎となることを願っている。コードを美しく保て。それがシステムを延命させる唯一の方法だ。
