【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)」を守るという、極めて重要な設計思想が隠されている。
「今、目の前の計算が合っているか」だけでなく、「一ヶ月後の自分が、この数値を見て正しく判断できるか」。この視点を持ってコードを書け。それが、世界標準のエンジニアへの第一歩だ。
