【実務・中級編】【上級者向け】大規模プロジェクトにおけるカスタムフィールドの計算負荷を最適化する – Project VBA解析バイブル

スポンサーリンク
スポンサーリンク

「計算の連鎖」を断ち切れ。大規模MS Projectにおけるカスタムフィールド最適化の極意

大規模なプロジェクト管理において、MS Projectは強力な武器になる。しかし、タスク数が数千、数万を超え、さらに複雑なカスタムフィールド(数式)が組み込まれた瞬間、その武器は「動作が重い、使い物にならないツール」へと変貌する。

多くの開発者が陥る罠がある。それは、「すべてをProjectの数式(FieldFormula)で解決しようとする」ことだ。

もし君が、業務自動化ツールを「動けばいい」レベルではなく、エンタープライズ級の「堅牢で高速なプロダクションコード」へと昇華させたいと考えているなら、今日この瞬間にその設計思想を捨てなければならない。

本稿では、計算負荷のボトルネックを特定し、オブジェクトのライフサイクルを制御することで、大規模プロジェクトでもストレスゼロで動作する、極限の最適化手法を伝授する。

1. なぜ「数式」はプロジェクトを殺すのか

MS Projectにおけるカスタムフィールドの数式は、非常に強力だ。しかし、その動作原理を知らなければ、設計ミスは致命的なパフォーマンス低下を招く。

依存関係の連鎖(Dependency Chain)の恐怖

Projectの計算エンジンは、あるフィールドの値が変更されると、その値に依存している他のすべてのフィールドを再計算しようとする。
例えば、以下のような構造を考えてみてほしい。

  • `Text1` = タスクの特定の属性
  • `Number1` = `Text1` を参照した計算
  • `Number2` = `Number1` を参照した計算
  • `Number3` = `Number2` を参照した計算

この状態で一つのタスクの属性を変更すると、エンジンは依存関係を辿り、連鎖的な再計算を走らせる。タスクが10,000件あれば、この連鎖は指数関数的に計算コストを増大させる。これが、数式を多用したプロジェクトが「一箇所直すだけで数分待たされる」正体だ。

「Pull型」から「Push型」への転換

従来の数式による設計は、Projectが値を「引きにいく(Pull)」設計だ。これに対し、我々プロフェッショナルが採用すべきは、VBAによって計算済みの値をフィールドに「送り込む(Push)」設計である。

「ロジックはVBAで実行し、結果だけをフィールドに書き込む」

この設計思想の転換こそが、最適化の第一歩である。

2. 高速化を実現するアーキテクチャ設計

大規模プロジェクトを制御するための、三つの鉄則を教えよう。

① 計算モードの完全制御

VBAで値を書き込む際、デフォルトの計算モード(Automatic)のままであれば、一回一回の書き込みごとに再計算が走る。これは自殺行為だ。必ず `Calculation` プロパティを `pjCalculationManual` に切り替え、一括処理が終わった後に一度だけ再計算させる。

② データの「一括処理」と「差分更新」

タスクを一つずつループして書き込むのは基本だが、さらに高度な手法として、計算ロジックを外部(メモリ上のDictionaryオブジェクトや、必要であれば外部DB)で完結させ、Projectへのアクセス回数を最小限に抑える設計が求められる。

③ 計算の「Dirty Flag」概念の導入

すべてのタスクを毎回計算する必要があるか? 否だ。変更があったタスク(Dirtyなタスク)のみを特定し、その周辺のみを更新するロジックを組む。これにより、計算対象を劇的に絞り込むことが可能になる。

3. 【実践】プロダクションレベルの最適化コード

以下に、大規模プロジェクトでも高速に動作する、カスタムフィールド一括更新モジュールのテンプレートを示す。このコードは、単なるループではなく、「状態の保護」「計算制御」「エラーハンドリング」を完備した、実戦投入可能な設計となっている。

Option Explicit

''' <summary>
''' カスタムフィールドの計算負荷を最小限に抑えつつ、
''' 大規模プロジェクトのタスク属性を一括更新する。
''' </summary>
Public Sub OptimizeCustomFieldUpdate()
    Dim t As Task
    Dim proj As Project
    Dim startTime As Double
    
    Set proj = ActiveProject
    startTime = Timer
    
    ' 1. 環境の保存と最適化設定
    ' 計算モードをマニュアルに、画面更新を停止することで、
    ' 書き込みごとの再計算と描画オーバーヘッドを完全に遮断する。
    Dim originalCalculation As Calculation
    originalCalculation = proj.Calculation
    
    On Error GoTo ErrorHandler
    
    Application.ScreenUpdating = False
    proj.Calculation = pjCalculationManual
    
    Debug.Print "--- Optimization Start ---"
    
    ' 2. 高速なデータ処理フェーズ
    ' ここでは例として、Text1の値を元にNumber1に計算結果を「Push」する。
    ' 数式(FieldFormula)ではなく、VBAで計算済みの値を直接代入する。
    
    Dim count As Long: count = 0
    
    ' タスクのループ。大規模な場合は、タスクのインデックスを利用した
    ' 配列処理やDictionaryとの連携を検討せよ。
    For Each t In proj.Tasks
        ' 有効なタスク(空行でない)のみを対象とする
        If Not t Is Nothing Then
            If Not t.ExternalTask Then ' プロジェクト内のタスクのみ
                
                ' --- ロジックの分離 ---
                ' 数式で行うべき複雑な計算を、ここ(VBA)で行う。
                ' Projectの数式エンジンを介さないため、極めて高速。
                Dim val1 As Double
                val1 = CalculateComplexLogic(t)
                
                ' 値の書き込み(ここでの書き込みは計算モードがManualなので高速)
                t.Number1 = val1
                
                count = count + 1
            End If
        End If
    Next t
    
    ' 3. 計算の最終実行
    ' 全ての書き込みが終わった後、一度だけ再計算を走らせる。
    ' これにより、依存関係の連鎖による重複計算を防ぐ。
    proj.Calculate
    
    Debug.Print "Processed Tasks: " & count
    Debug.Print "Execution Time: " & Round(Timer - startTime, 2) & " sec"
    Debug.Print "--- Optimization Complete ---"

CleanUp:
    ' 4. 環境の復元(必ず実行すること)
    Application.ScreenUpdating = True
    proj.Calculation = originalCalculation
    Exit Sub

ErrorHandler:
    MsgBox "Critical Error during optimization: " & Err.Description, vbCritical
    Resume CleanUp
End Sub

''' <summary>
''' 複雑な計算ロジックをカプセル化する。
''' 数式(FieldFormula)に書く代わりに、この関数にロジックを集約させる。
''' </summary>
Private Function CalculateComplexLogic(ByRef t As Task) As Double
    ' 例: 期間やリソースコストに基づいた独自の重み付け計算
    ' 数式では表現が困難、あるいは重すぎるロジックをここに記述。
    On Error Resume Next
    Dim result As Double
    result = (t.Duration / 480) * 1.5 ' 仮のロジック
    
    If Err.Number <> 0 Then
        result = 0
    End If
    
    CalculateComplexLogic = result
End Function

4. 運用における「致命的なミス」を避けるために

この設計を実務に導入する際、以下の点に注意せよ。これを見落とすと、ツールは再び脆弱なものへと退行する。

データベース・ファイル連携時の注意点

もし、このVBAから外部のSQL ServerやExcelファイルからデータを取得して書き込む場合、「Projectへのアクセス」と「外部データへのアクセス」を混同してはならない。

  • 悪い例: `For Each t In Tasks` の中で、毎回SQLを発行して `SELECT` する。これは地獄のような遅延を生む。
  • 良い設計: 最初に外部データをすべて `Scripting.Dictionary` 等のメモリ上にロードし、Projectのループ内ではメモリ上の値を参照する。

保守性の確保:ロジックの「一元管理」

「数式」は、プロジェクトファイルそのものにロジックを埋め込んでしまう。これは、ロジックを変更したいときに、全プロジェクトファイルを開いて修正しなければならないことを意味する。
我々が提唱する「VBAによるPush型」であれば、ロジックはアドイン(.xlam)や共通モジュールに集約できる。「ロジックはコードに、値はフィールドに」。これが、大規模運用における保守性の正解だ。

結論

大規模プロジェクトにおけるパフォーマンス問題の本質は、計算エンジンの「過剰な反応」にある。

エンジニアとしての真価は、Projectの機能に依存し切ることではなく、その裏側で動く計算メカニズムを理解し、あえてその制御を奪い取る(Manualモードへの切り替えとVBAによるPush)ことにある。

この設計思想をマスターすれば、君が構築するツールは、どんなに巨大なプロジェクトであっても、正確かつ、驚異的なスピードで動作し続けるだろう。

タイトルとURLをコピーしました