【極限の知見】数千行のカスタムフィールドを瞬時に「無」に帰す:MS Project VBA 高速化の真髄
現場で「動くツール」を作っている人間は多い。しかし、「プロフェッショナルなツール」を作れる人間は極めて稀だ。
君がもし、数千行のタスクを抱える大規模プロジェクトの管理ツールを開発しているなら、一度立ち止まって考えてほしい。「カスタムフィールドの値をクリアする」という、一見シンプルに見える処理に、どれほどのコストを支払っているか?
もし君が、`For Each` でタスクを回し、一行ずつ `Task.Text1 = “”` と書き込むだけのコードを書いているなら、それは設計ミスだ。データ量が増大した瞬間、ユーザーの画面はフリーズし、君へのクレームは「重い」という言葉と共に押し寄せるだろう。
本稿では、MS Projectのオブジェクトモデルの深淵に触れ、数千行のカスタムフィールドを一括消去するための、計算量とオーバーヘッドを最小化する「極限の高速化テクニック」を伝授する。
1. なぜ、あなたのコードは遅いのか?:Recalculation Stormの正体
初級者が書くコードは、以下の構造だ。
' 【非効率なコードの典型例】
Sub BadPractice_ClearFields()
Dim t As Task
For Each t In ActiveProject.Tasks
If Not t Is Nothing Then
t.Text1 = "" ' ← ここが致命的なボトルネック
t.Text2 = ""
End If
Next t
End Sub
なぜこれが遅いのか? 原因はコードの書き方そのものではなく、MS Projectの「再計算(Recalculation)エンジン」にある。
MS Projectは、タスクのプロパティ(特にカスタムフィールド)が変更されるたびに、「この変更によってスケジュールや計算式、依存関係に影響が出るか?」を検証しようとする。つまり、1つの値を書き換えるたびに、プロジェクト全体の再計算プロセスがトリガーされる。
これを繰り返すと、「値の書き換え → 再計算 → 書き換え → 再計算…」という、いわゆる Recalculation Storm(再計算の嵐) が発生する。これが、処理時間が指数関数的に増大する真犯人だ。
2. 勝利への設計指針:3つの鉄則
プロフェッショナルは、オブジェクトの状態を制御することで、この嵐を鎮める。
① Calculation モードの制御(最優先事項)
MS Projectの計算モードを `pjManual` に切り替える。これにより、値の変更がプロジェクト全体のスケジュールに及ぼす影響の検証を一時停止できる。これが最も劇的な効果をもたらす。
② ScreenUpdating の抑制
画面更新を止めるのは基本中の基本だ。描画処理は極めてコストが高い。描画を止めることで、CPUリソースを純粋なロジックに集中させる。
③ オブジェクトの生存確認とエラーハンドリング
大規模プロジェクトでは、タスクの削除やフィルタリングによって、`Tasks` コレクション内に `Nothing` が混在することがある。これを見逃すと、実行時エラーでツールは即死する。
3. プロダクション・グレードの実装例
以下に、私が実務で推奨している、堅牢かつ高速な実装パターンを示す。このコードは、単なる「クリア処理」ではなく、「システムの実行状態を適切に管理する」設計思想に基づいている。
''' <summary>
''' 数千行規模のタスクに対してカスタムフィールドを一括クリアする。
''' 計算モードと画面更新を制御し、オーバーヘッドを最小化する。
''' </summary>
Public Sub FastClearCustomFields()
Dim targetProject As Project
Dim t As Task
Dim originalCalculation As Calculation
Dim startTime As Double
' 実行時間の計測(パフォーマンス評価用)
startTime = Timer
On Error GoTo ErrorHandler
Set targetProject = ActiveProject
' --- [Phase 1: 状態の保存と抑制] ---
' 現在の計算モードを保存(後で復元するため)
originalCalculation = targetProject.Calculation
' 画面更新と計算を停止(これが高速化の核)
Application.ScreenUpdating = False
targetProject.Calculation = pjManual
Debug.Print "Starting massive clear operation..."
' --- [Phase 2: 高速ループ実行] ---
' コレクションを直接回し、Nothingチェックを厳格に行う
For Each t In targetProject.Tasks
' タスクが存在しない(削除済みなど)場合はスキップ
If Not t Is Nothing Then
' 複数のフィールドをクリアする場合、
' プロパティへのアクセス回数を最小限にするのが定石
t.Text1 = ""
t.Text2 = ""
t.Text3 = ""
' 必要に応じて追加
End If
Next t
' --- [Phase 3: 状態の復元] ---
' ユーザーの環境を壊さないよう、元の設定に戻す
targetProject.Calculation = originalCalculation
Application.ScreenUpdating = True
MsgBox "処理が完了しました。" & vbCrLf & _
"実行時間: " & Format(Timer - startTime, "0.00") & " 秒", vbInformation, "Success"
ExitSub:
Exit Sub
ErrorHandler:
' エラーが発生した場合でも、必ず画面更新と計算モードを復元する
' これを怠ると、MS Projectが「使い物にならない状態」で止まってしまう
Application.ScreenUpdating = True
If Not targetProject Is Nothing Then
targetProject.Calculation = originalCalculation
End If
MsgBox "致命的なエラーが発生しました。" & vbCrLf & _
"Error No: " & Err.Number & vbCrLf & _
"Description: " & Err.Description, vbCritical, "Critical Error"
Resume ExitSub
End Sub
4. アーキテクトからの警告:データベース連携における注意点
もし君が、このクリア処理の結果を外部のデータベース(SQL ServerやAccess)やExcelファイルに書き出そうとしているなら、さらに注意が必要だ。
1. トランザクションの意識:
VBA側で大量の値を書き換えた直後に外部DBへ書き込む場合、MS Project側のメモリ上のデータと、DB側の整合性が一時的に崩れるリスクがある。必ず `targetProject.Calculation = pjAutomatic` で計算を完了させてから、外部へのエクスポート処理に移ること。
2. メモリの解放:
大規模なオブジェクト操作を繰り返す場合、`Set t = Nothing` をループの最後に入れる必要はないが、プロシージャを抜ける際に `Set targetProject = Nothing` を明示的に行うことで、COMオブジェクトのライフサイクルを制御し、メモリリークを防止する癖をつけておくべきだ。
結論
「動けばいい」という思考は、スケールしないシステムを生む。
MS Project VBAにおける高速化とは、単に速い書き方を探すことではない。「アプリケーションがいかにして計算し、描画し、メモリを管理しているか」という内部メカニズムを理解し、それを制御することだ。
この設計パターンをマスターすれば、君のツールは「重い」という批判を浴びることはなくなる。代わりに、「驚くほどスムーズだ」という賞賛を受けることになるだろう。
