【実務・中級編】【中級者向け】カスタムフィールドの計算結果を静的な値に変換して保存する – Project VBA解析バイブル

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

【Project VBA】動的計算を「履歴」へと昇華させる:カスタムフィールドの静的値変換(Snapshot)実装論

プロジェクト管理の現場において、Microsoft Projectは強力なエンジンだ。しかし、その「強力さ」ゆえに、多くのエンジニアが陥る致命的な罠がある。それが「カスタムフィールドの計算式(Formula)への過度な依存」だ。

計算式は便利だ。スケジュールが変われば、リアルタイムに値が更新される。だが、ビジネスにおける「報告」や「監査」において、リアルタイム性は時に「毒」となる。先月の進捗報告で提示した数値が、プロジェクトのスケジュール変更によって勝手に書き換わっていたとしたら? そのツールは、管理ツールとしての信頼性を失い、単なる「動く計算機」に成り下がる。

本稿では、動的な計算結果を特定のタイミングで「静的な値(Value)」として固定し、履歴(スナップショット)として保存するための、堅牢かつプロフェッショナルな実装手法を伝授する。

1. なぜ「計算式」だけでは不十分なのか:状態(State)とプロセス(Process)の混同

設計思想の観点から言えば、カスタムフィールドの `Formula` プロパティは「プロセス(計算手順)」を定義するものであり、`Value` プロパティは「状態(結果としての値)」を保持するものである。

多くの開発者は、`Formula` を書き換えることで全てのロジックを完結させようとする。しかし、プロジェクトの履歴管理(Baselineとの比較や月次報告)を行う場合、「その時点での状態」を物理的に保存しなければならない。

  • Formula(動的): 「A + B を計算せよ」という命令。入力が変われば結果も変わる。
  • Value(静的): 「150」という確定した事実。入力が変わっても変わらない。

実務レベルの自動化ツールを設計するなら、「計算は動的なフィールドで行い、確定した瞬間に静的なフィールドへ値を転記する」という、二段構えのアーキテクチャを採用すべきだ。

2. 堅牢な設計指針:バグを生まないための3つの鉄則

実装に入る前に、アーキテクトとして以下の設計原則を脳に刻んでほしい。

① オブジェクトのライフサイクルを意識せよ

Projectの `Task` コレクションをループで回す際、`Nothing`(空のタスク)やサマリータスクの扱いを誤ると、ランタイムエラーでプロセスが停止する。必ずオブジェクトの存在確認を行うこと。

② 再計算の連鎖(Recalculation Loop)を制御せよ

値を書き換えるたびにProjectが再計算を走らせると、大規模プロジェクトではパフォーマンスが指数関数的に低下する。書き込み処理を行う際は、計算モードの制御を検討せよ。

③ マジックナンバーを排除せよ

`Task.Text1` や `Task.Number1` を直接コードに書き込むのは素人の仕事だ。どのフィールドが「計算用」で、どのフィールドが「保存用」なのかを定数として定義し、保守性を確保せよ。

3. 実装例:Snapshotエンジン

以下に、計算済みのカスタムフィールドから、履歴保存用のフィールドへ値を一括コピーする、プロダクションレベルのコードを示す。

Option Explicit

' ==============================================================================
' Module: modSnapshotEngine
' Description: カスタムフィールドの計算結果を静的な値としてバックアップする
' Author: Chief Architect
' ==============================================================================

' フィールド定義の定数化(保守性を高めるための必須事項)
' ここを変更するだけで、プロジェクト全体の設計変更に対応可能
Private Const SRC_FIELD_INDEX As Integer = 1  ' 例: Number1 (計算式が入っているフィールド)
Private Const DST_FIELD_INDEX As Integer = 2  ' 例: Number2 (履歴として保存するフィールド)

''' <summary>
''' 指定したタスクの計算結果を、履歴用フィールドへ静的にコピーする
''' </summary>
Public Sub ExecuteSnapshot()
    Dim pj As Project
    Set pj = ActiveProject
    
    Dim t As Task
    Dim snapshotCount As Long
    Dim errorCount As Long
    
    ' パフォーマンス向上のための準備
    ' ※ Project VBAにはExcelのような完全なScreenUpdating制御はないが、
    ' 計算モードを制御することで速度を劇的に改善できる
    On Error GoTo ErrorHandler
    
    ' 計算モードをマニュアルに設定(書き込み中の再計算による遅延を防ぐ)
    pj.Calculation = pjCalculationManual
    
    snapshotCount = 0
    errorCount = 0

    Debug.Print "--- Snapshot Process Started: " & Now & " ---"

    ' タスクコレクションのループ
    For Each t In pj.Tasks
        ' 1. オブジェクトの存在確認(Nothingチェック)
        ' 2. タスクが削除されていないか、有効なオブジェクトかを判定
        If Not t Is Nothing Then
            
            ' サマリータスクを除外するかどうかは要件次第。
            ' ここでは、個別のタスク(Workタスク)のみを対象とする
            If Not t.Summary Then
                On Error Resume Next
                ' 計算結果(Value)を、保存用フィールド(Value)へ直接代入
                ' FormulaではなくValueを参照することで、計算済みの「結果」を取得する
                t.NumberField(DST_FIELD_INDEX).Value = t.NumberField(SRC_FIELD_INDEX).Value
                
                If Err.Number <> 0 Then
                    errorCount = errorCount + 1
                    Debug.Print "Error on Task: " & t.Name & " - " & Err.Description
                    Err.Clear
                Else
                    snapshotCount = snapshotCount + 1
                End If
                On Error GoTo ErrorHandler
            End If
            
        End If
    Next t

    ' 計算モードを自動に戻す
    pj.Calculation = pjCalculationAutomatic
    
    MsgBox "スナップショット完了" & vbCrLf & _
           "成功: " & snapshotCount & " 件" & vbCrLf & _
           "失敗: " & errorCount & " 件", vbInformation, "Execution Result"
    
    Debug.Print "--- Snapshot Process Completed ---"
    Exit Sub

ErrorHandler:
    ' エラー発生時のクリーンアップ
    pj.Calculation = pjCalculationAutomatic
    MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical, "Critical Error"
End Sub

' ==============================================================================
' 補足: フィールド指定のテクニック
' Project VBAでは、Text1, Number1などは、NumberField(1) のように
' インデックス指定でアクセスできる。これにより定数管理が可能になる。
' ==============================================================================

4. プロフェッショナルのための高度なアドバイス

データ整合性のための「トリガー」設計

このコードをいつ実行するか? これが運用の鍵だ。
おすすめは、「特定のフラグが立った時」または「レポート出力ボタンを押した時」に実行することだ。
例えば、`Text5` フィールドに “SNAPSHOT_READY” という文字列が入っているタスクのみを対象にする、といったフィルタリングロジックを `If` 文に加えるだけで、ツールとしての堅牢性は飛躍的に向上する。

ファイル・データベース連携への拡張

もし、このスナップショットをProjectファイル内ではなく、外部のSQL ServerやExcel、あるいはCSVに書き出したい場合は、この `For Each t In pj.Tasks` のループ内で、値をオブジェクトのプロパティとして保持し、一括で外部ライブラリ(ADO等)へ渡す設計にせよ。

「Projectの中だけで完結させる」という思考停止から脱却したとき、君の書くコードは単なるスクリプトから、真の「エンタープライズ・ソリューション」へと進化する。

結びに

カスタムフィールドの動的値を静的に変換する手法は、一見すると単純なコピー作業に見える。しかし、その裏側には「データの不変性(Immutability)」を守るという、極めて重要な設計思想が隠されている。

「今、目の前の計算が合っているか」だけでなく、「一ヶ月後の自分が、この数値を見て正しく判断できるか」。この視点を持ってコードを書け。それが、世界標準のエンジニアへの第一歩だ。

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