これは、単なる「コードの書き換え」の記録ではない。
数千、数万のタスクが複雑に絡み合うMicrosoft Projectという「巨大な計算機」を、いかにして手なずけ、沈黙させ、そして最速で回すかという、アーキテクトとしての矜持を込めた技術論である。
現場に放置された数年前のレガシーなProject VBAは、往々にして不必要なオブジェクトの参照、無秩序な再計算、そして不透明なエラーハンドリングの塊だ。これらを現代的な、堅牢かつ高速なアーキテクチャへと昇華させるための極限の知見をここに記す。
—
【レガシー保守】Project VBAを再定義する:高速化とモダン・リファクタリングの極致
1. 速度を殺す「暗黙の計算」を封じ込める
Project VBAがExcel VBAよりも圧倒的に遅くなる最大の理由は、タスクを一つ変更するたびに走る「クリティカルパスの再計算」と「UIの再描画」である。レガシーなコードは、これを放置したままループを回している。
まず、我々がなすべきは、Projectのエンジンを一時的に「仮死状態」にすることだ。
‘ — プロジェクト・エンジンの制御ユーティリティ —
Public Sub OptimizeEngine(ByVal isOptimizing As Boolean)
With Application
If isOptimizing Then
‘ 描画停止と手動計算への移行
.ScreenUpdating = False
.Calculation = pjManual
Else
‘ 復元:再計算を実行し描画を再開
.Calculation = pjAutomatic
.ScreenUpdating = True
End If
End With
End Sub
2. オブジェクト・トラバーサルの最適化
レガシーコードにおける最大の罪過は、ループ内での `ActiveProject.Tasks(i)` や `ActiveSelection` への頻繁なアクセスである。COMオブジェクトの参照解決はコストが高い。
悪い例(レガシー)
‘ ループのたびにCOMオブジェクトの解決が走り、パフォーマンスが指数関数的に低下する
Dim i As Long
For i = 1 To ActiveProject.Tasks.Count
If Not ActiveProject.Tasks(i) Is Nothing Then
ActiveProject.Tasks(i).Text1 = “Updated”
End If
Next i
現代的なアプローチ(最適化後)
コレクションをローカル変数にキャッシュし、`For Each` を用いる。`For Each` は内部的に `IEnumVARIANT` を使用するため、インデックス指定よりも高速だ。
Dim tsk As MSProject.Task
Dim tsks As MSProject.Tasks
Set tsks = ActiveProject.Tasks
‘ コレクションをキャッシュし、直接参照を最小化する
For Each tsk In tsks
If Not tsk Is Nothing Then
‘ 外部参照を減らし、プロパティへの代入を局所化
tsk.Text1 = “Optimized”
End If
Next tsk
Set tsks = Nothing ‘ 明示的な解放
3. Windows APIによるUI制御の極限
`Application.ScreenUpdating` だけでは不十分な場合がある。特に大規模なデータのインポートや更新時、ProjectのメインウィンドウがOSからの再描画要求に反応してしまうのを完全に遮断するには、Windows APIでウィンドウプロシージャの描画をロックする。
If VBA7 Then
Private Declare PtrSafe Function LockWindowUpdate Lib “user32” (ByVal hwndLock As LongPtr) As Long
Else
Private Declare Function LockWindowUpdate Lib “user32” (ByVal hwndLock As Long) As Long
End If
‘ 使用例
Public Sub MassiveUpdate()
On Error GoTo CleanUp
‘ プロジェクトのメインウィンドウハンドルをロック
LockWindowUpdate Application.HWND
‘ — 高速処理を実行 —
CleanUp:
‘ ロック解除(0を渡す)
LockWindowUpdate 0
End Sub
4. メモリ管理とオブジェクトのライフサイクル
Project VBAはCOM参照のカウントに極めて敏感だ。特に大規模なプロジェクトでリソース、タスク、カレンダーを操作する場合、オブジェクトを使い終わった直後に `Set object = Nothing` を行う習慣は、単なるマナーではなく「メモリリークによるクラッシュ回避」の必須要件となる。
また、`Task` オブジェクトを操作する際、プロパティへのアクセス回数を減らすため、`With` ブロックを多用するのではなく、必要な情報を一度配列(Variant Array)に読み込み、処理後に一括で書き戻す手法を検討すべきだが、ProjectのタスクオブジェクトはExcelのRangeほど単純ではない。そのため、「プロパティへのアクセスは必要最小限にする」という基本設計が肝要となる。
5. 堅牢なエラーハンドリング:モダン・パターン
レガシーな `On Error Resume Next` の乱用は、沈黙のデータ破壊を招く。現代的なVBAでは、エラースタックを意識した構造化ハンドリングを実装する。
Public Sub ModernTaskProcessor()
Const PROC_NAME As String = “ModernTaskProcessor”
On Error GoTo ErrHandler
‘ 1. 前処理
OptimizeEngine True
Dim tsk As Task
For Each tsk In ActiveProject.Tasks
If Not tsk Is Nothing Then
‘ 2. ビジネスロジック
ProcessTaskInternal tsk
End If
Next tsk
CleanUp:
‘ 3. 後処理(エラー時も必ず通る)
OptimizeEngine False
Exit Sub
ErrHandler:
‘ システム管理者向けのログ出力やエラー詳細の構築
Debug.Print “Error in ” & PROC_NAME & “: ” & Err.Description
‘ 必要に応じてカスタムエラーログ関数を呼ぶ
Resume CleanUp
End Sub
Private Sub ProcessTaskInternal(ByRef tsk As Task)
‘ ロジックを分離し、責務を明確化する
tsk.ActualCost = tsk.Cost 0.9 ‘ 例:コストの再計算
End Sub
6. システム間連携のボトルネック解消
古いコードでは、ProjectからExcelへデータを1セルずつ転送しているものが多い。これは最悪のパフォーマンスを生む。
現代的な手法では、データを一度「CSV文字列」または「2次元配列」としてメモリ上に構築し、Excelの `Range.Value = Array` で一括出力するか、ADO(ActiveX Data Objects)を利用してメモリ上のレコードセットとして扱うべきである。
結論:保守とは「命」を吹き込む作業である
古いProject VBAをモダン化することは、単にバグを取り除く作業ではない。それは、複雑化したプロジェクト管理という業務プロセスに、再び「速度」と「信頼」という命を吹き込む作業だ。
1. 計算エンジンを制御せよ。
2. COMオブジェクトの参照を最小化せよ。
3. APIを駆使してUIを沈黙させよ。
4. オブジェクトのライフサイクルを厳格に管理せよ。
この4原則を遵守すれば、数分かかっていたバッチ処理は数秒へと劇的に進化する。我々アーキテクトに求められているのは、常に「枯れた技術」の中に「最先端の効率」を見出すことなのだ。
