【実務・中級編】【上級者向け】Projectの計算エンジンをバイパスしてカスタムフィールドを更新する手法 – Project VBA解析バイブル

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

計算エンジンの呪縛を解け:MS Project VBAにおける「直接書き込み」による極限の高速化戦略

プロジェクトの規模が数千、数万タスクに膨れ上がったとき、多くの開発者が直面する「壁」がある。それは、MS Projectの計算エンジンによるパフォーマンスの低下だ。

カスタムフィールド(Text1やNumber1など)に計算式(Formula)を埋め込み、それによってプロジェクトの状態を可視化するのは、一見すると「正しい」設計に見える。しかし、大規模なスケジュールの更新、外部データベースとの同期、あるいは複雑な条件分岐を伴う自動化処理において、この「計算式への依存」は致命的なボトルネックへと変貌する。

本記事では、MS Projectの計算エンジンをバイパスし、メモリ上で計算を完結させてから値を直接フィールドへ流し込む、プロフェッショナル向けの「Direct Value Injection(直接値注入)」手法を伝授する。

1. なぜ「計算式(Formula)」はプロの現場で忌避されるのか

まず、なぜ君たちが書いたツールが、現場の巨大なプロジェクトファイルで「重い」と言われるのか、その構造的な理由を理解しなければならない。

計算エンジンのオーバーヘッド

MS Projectの計算エンジンは、タスク間の依存関係(Predecessors/Successors)やリソースの割り当てを考慮した、非常に高度な計算グラフを保持している。カスタムフィールドに数式を設定すると、以下の連鎖が発生する。

1. トリガー: タスクの期間や日付が変更される。
2. 再計算の波及: 依存関係にある全タスクに対して、計算エンジンが走る。
3. 計算式の実行: 各タスクのカスタムフィールドの数式が評価される。
4. 再計算のループ: 数式の計算結果が、さらに別の計算(コスト計算やリソース平準化)に影響を与える場合、計算は再帰的に繰り返される。

タスク数が10,000を超えた環境で、VBAからループを回してプロパティを書き換えるたびにこのプロセスが走ると、計算量は $O(n^2)$ に近い挙動を示し、ツールは実用不可能なレベルまで低速化する。

「ブラックボックス」としての数式

数式は、VBAのデバッグプロセスから隔離されている。数式によって値が変わる場合、エラーが発生した際に「それがデータの異常なのか、数式のロジックミスなのか」の切り分けが困難になる。これは、堅牢な業務システムを構築する上で致命的な欠陥だ。

2. アーキテクチャの転換:計算の主権をVBAへ

プロフェッショナルな設計では、「計算のロジックはVBA(または外部DB)に集約し、MS Projectは単なる『データストア(器)』として扱う」という考え方を取る。

具体的には、以下の3ステップでプロセスを構築する。

1. Decoupling(分離): カスタムフィールドに設定されている既存の数式をクリア(空文字化)する。
2. In-Memory Calculation(メモリ内計算): Projectのエンジンを使わず、VBAのメモリ上で全てのロジックを完結させる。
3. Batch Update(一括書き込み): 計算結果を、計算エンジンを停止させた状態で直接フィールドに書き込む。

3. 実装ガイド:プロダクション・クオリティのコード

以下に、タスクのステータスを複数の属性(完了率、遅延、重要度)から判定し、カスタムフィールド `Text1` に書き込む実用的なコードを示す。

このコードの肝は、`Application.Calculation = pjManual` によるエンジンの停止と、計算式を事前に抹消するプロセスにある。

Option Explicit

''' <summary>
''' プロジェクトのカスタムフィールドを高速に更新するメインルーチン
''' </summary>
Public Sub UpdateCustomFieldsHighPerformance()
    Dim proj As Project
    Set proj = Application.ActiveProject
    
    ' 1. パフォーマンス確保のための初期設定
    ' 計算モードをマニュアルに切り替え、画面更新を停止する
    Application.Calculation = pjManual
    Application.ScreenUpdating = False
    
    On Error GoTo ErrorHandler
    
    ' 2. 既存の「数式」という呪縛を解く
    ' 計算式が残っていると、書き込みのたびに再計算が走るため、必ずクリアする
    PrepareFields proj
    
    ' 3. 高速書き込みフェーズ
    ' 計算ロジックをタスクごとに分離せず、可能な限りメモリ内で処理する
    Dim t As Task
    Dim statusValue As String
    
    Debug.Print "Update Start: " & Now
    
    For Each t In proj.Tasks
        ' 空タスク(サマリータスク等)のハンドリング
        If Not t Is Nothing Then
            If Not t.IsNull Then
                ' --- ここにビジネスロジックを集約 ---
                ' MS Projectの計算エンジンに頼らず、VBAのロジックで判定を行う
                statusValue = DetermineTaskStatus(t)
                
                ' 直接値を書き込む(Formulaが空なので、極めて高速)
                t.Text1 = statusValue
                ' ----------------------------------
            End If
        End If
    Next t
    
    Debug.Print "Update End: " & Now

CleanUp:
    ' 4. 状態の復元
    ' 異常終了時でも必ず計算モードを自動に戻す必要がある
    Application.Calculation = pjAutomatic
    Application.ScreenUpdating = True
    Exit Sub

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

''' <summary>
''' カスタムフィールドの数式をクリアする
''' </summary>
Private Sub PrepareFields(ByRef proj As Project)
    Dim t As Task
    ' 全タスクを走査してFormulaを消去(大規模な場合は、特定のフィールドに限定すべき)
    ' ここではText1を対象とする
    For Each t In proj.Tasks
        If Not t Is Nothing Then
            If Not t.IsNull Then
                If t.Formula(pjText) <> "" Then
                    t.Formula(pjText) = ""
                End If
            End If
        End If
    Next t
End Sub

''' <summary>
''' ビジネスロジック:タスクのステータスを判定する
''' </summary>
Private Function DetermineTaskStatus(ByRef t As Task) As String
    ' 例: 完了率、遅延、重要度に基づく複雑な判定
    ' Projectの計算式では記述が困難な、多段のIf文や外部参照もここなら自由自在だ
    
    Dim isDelayed As Boolean
    Dim isCritical As Boolean
    
    ' 遅延判定(現在日と比較)
    isDelayed = (t.Finish < Date And t.PercentComplete < 100)
    
    ' 重要度判定(ユーザー定義フィールド等を利用)
    isCritical = (t.Priority > 500)
    
    ' 判定ロジック
    If isDelayed And isCritical Then
        DetermineTaskStatus = "CRITICAL_DELAY"
    ElseIf isDelayed Then
        DetermineTaskStatus = "DELAYED"
    ElseIf t.PercentComplete = 100 Then
        DetermineTaskStatus = "COMPLETED"
    Else
        DetermineTaskStatus = "ON_TRACK"
    End If
End Function

4. 堅牢な設計のためのチーフアーキテクトの助言

この手法を実務に投入する際、君たちが陥りやすい罠と、それを回避するための設計指針を伝える。

① データの「真実の源(Single Source of Truth)」を明確にせよ

カスタムフィールドに値を直接書き込む手法を採用する場合、そのフィールドはもはや「計算結果」ではなく「確定したデータ」となる。
もし、ユーザーが手動でそのフィールドを書き換えた場合、VBAを再実行すると上書きされてしまう。これを防ぐには、「自動更新された値」と「手動入力値」を区別するフラグ用フィールドを別途用意するのが、プロの設計だ。

② データベース連携におけるトランザクション

外部データベース(SQL ServerやAccess等)からデータを取得してカスタムフィールドに反映させる場合、必ず 「DB読み込み → メモリ内計算 → Projectへの一括書き込み」 というフローを守れ。
ループの中で1行ずつSQLを発行し、その都度Projectのフィールドを更新するような設計は、最悪のパフォーマンスを招く。

③ エラーハンドリングの徹底

`Application.Calculation = pjManual` を実行したままエラーで終了すると、ユーザーは「なぜ計算が更新されないんだ!」とパニックに陥る。
`On Error GoTo` を使い、必ず `CleanUp` セクションで `pjAutomatic` に戻す設計を徹底しろ。これは「作法」ではなく「義務」だ。

結論

MS Project VBAにおける真のマスターとは、ツールの機能を使いこなす者だけを指すのではない。ツールの限界(計算エンジンのオーバーヘッド)を知り、その限界を回避するために、自らのコードで制御権を取り戻せる者のことだ。

計算式に頼る「楽な開発」を捨て、メモリ内でロジックを支配する「強い開発」を選択せよ。それが、大規模プロジェクトを支えるエンジニアとしての、唯一の道である。

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