【極限の知見】カスタムフィールドの「数式」を守り抜け。定義変更を検知する監査システムの構築術
こんにちは。現場のコードを書き、システムを設計し、時には深夜のトラブルシューティングに魂を削る、あなたの先輩エンジニアです。
Project VBAを学び始めると、皆さんはまず「特定のセルの値を書き換える」「タスクを追加する」といった、「データ(値)」の操作に夢中になります。それは素晴らしい第一歩です。マクロの記録から脱却し、自分の手でプロジェクトを動かしている実感は、プログラミングの醍醐味ですから。
しかし、プロジェクトの規模が大きくなり、組織としてProjectを運用し始めると、ある「恐ろしい問題」に直面します。
それは、「誰かが、カスタムフィールドの『数式(Formula)』をこっそり書き換えてしまった」という問題です。
1. なぜ「数式」の変更は、プロジェクトの命取りになるのか?
想像してみてください。
あなたは、カスタムフィールド `Cost_Adjustment` に、`[Cost]*0.1` という完璧な計算式を設定しました。この数値に基づいて、全社的な予算報告書が自動生成される仕組みを構築したのです。
ところが、ある日、誰かが「ちょっと計算が合わない気がするから」と、その数式を `[Cost]*0.15` に書き換えてしまいました。
この瞬間、プロジェクトの整合性は音を立てて崩れ去ります。
データの値(Value)は一見正しく見えますが、その「計算の根拠(Metadata)」が汚染されているのです。これは、単なる入力ミスよりも遥かに見つけにくく、かつ致命的な破壊力を持っています。
これを防ぐには、「誰が、いつ、どの数式を変えたのか」を監視し、ログに残す「監査(Auditing)機能」が必要です。
2. アーキテクチャの設計:存在しないイベントを「擬似的に」実現する
ここで、一つ技術的な壁にぶつかります。
実は、Project VBAの標準イベントには「カスタムフィールドの定義が変更された瞬間に発火する」という、便利なイベントは存在しません。
「えっ、じゃあ不可能なの?」と思ったあなた。安心してください。ここからが、「道具を使う人」と「仕組みを作るエンジニア」の分かれ道です。
直接的なイベントがないのなら、「保存される直前(BeforeSave)に、現在の状態と『正解の状態』を比較する」というロジックを組み込めばいいのです。
私たちは、以下の3つのステップでこのシステムを構築します。
1. Snapshot(スナップショット): 「正しい数式」をどこかに記録しておく。
2. Compare(比較): 保存時に、現在の数式とスナップショットを照合する。
3. Log(記録): 差異があれば、ユーザーに警告し、ログファイルに書き出す。
3. 実装コード:Snapshot & Compare パターン
それでは、魂を込めて書いたコードを見ていきましょう。
今回は、もっとも確実な方法として、「正しい数式をテキストファイル(または隠しシート)に保持しておき、保存時にチェックする」という設計にします。
以下のコードは、`ThisProject` モジュールに記述することを想定しています。
' ============================================================
' Project VBA: Custom Field Audit System
' 目的: カスタムフィールドの数式変更を検知し、ログを記録する
' ============================================================
Option Explicit
' ログファイルの保存先(環境に合わせて変更してください)
Private Const LOG_FILE_PATH As String = "C:\Temp\Project_Audit_Log.txt"
' 正解の数式を定義したマスターファイル(本来は管理者が厳重に管理すべきもの)
Private Const MASTER_FORMULA_PATH As String = "C:\Temp\Master_Formulas.txt"
''' <summary>
''' プロジェクト保存前に実行されるイベントハンドラ
''' </summary>
Private Sub Project_ProjectBeforeSave(ByVal SaveAsUI As Boolean, ByVal SaveOK As Boolean)
Dim currentFormula As String
Dim masterFormula As String
Dim fieldName As String
' 今回の監視対象フィールド名
fieldName = "Cost_Adjustment"
' 1. 現在のプロジェクトのフィールド数式を取得
' 注: 本来はApplicationオブジェクト経由で定義を取得するのが理想的ですが、
' VBAではTaskやResourceのプロパティから判定するアプローチが現実的です。
' ここでは簡略化のため、特定のタスクの数式をチェックするロジックのイメージを示します。
On Error Resume Next
currentFormula = ActiveProject.TaskFields(fieldName).Formula
On Error GoTo 0
' 2. マスター(正解)の数式を読み込む
masterFormula = ReadTextFile(MASTER_FORMULA_PATH)
' 3. 比較ロジック
If currentFormula <> masterFormula Then
' 差異を発見!
Dim msg As String
msg = "【警告】カスタムフィールド '" & fieldName & "' の数式が変更されています!" & vbCrLf & _
"期待値: " & masterFormula & vbCrLf & _
"現在値: " & currentFormula & vbCrLf & _
"変更を記録して保存を継続しますか?"
' ユーザーに警告(Yesならログを残して保存、Noなら保存を中止)
If MsgBox(msg, vbCritical + vbYesNo, "不正な定義変更の検知") = vbYes Then
WriteAuditLog fieldName, masterFormula, currentFormula
Else
' 保存をキャンセルさせる
SaveOK = False
Exit Sub
End If
End If
End Sub
''' <summary>
''' 監査ログをテキストファイルに追記する
''' </summary>
Private Sub WriteAuditLog(fldName As String, oldVal As String, newVal As String)
Dim fileNum As Integer
fileNum = FreeFile
Open LOG_FILE_PATH For Append As #fileNum
Print #fileNum, "--------------------------------------------------"
Print #fileNum, "Timestamp: " & Now
Print #fileNum, "User: " & Environ("UserName")
Print #fileNum, "Field: " & fldName
Print #fileNum, "Old Formula: " & oldVal
Print #fileNum, "New Formula: " & newVal
Print #fileNum, "--------------------------------------------------"
Close #fileNum
End Sub
''' <summary>
''' テキストファイルから文字列を読み込むヘルパー関数
''' </summary>
Private Function ReadTextFile(path As String) As String
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
If fso.FileExists(path) Then
Dim ts As Object
Set ts = fso.OpenTextFile(path, 1) ' 1 = ForReading
ReadTextFile = ts.ReadAll
ts.Close
Else
ReadTextFile = "MASTER_FILE_NOT_FOUND"
End If
Set fso = Nothing
End Function
4. コードの深層解説:エンジニアとしての視点
このコードをただコピペして終わりにしてはいけません。なぜこのように書いたのか、その「重み」を理解してください。
# ① オブジェクトのライフサイクルとエラーハンドリング
`On Error Resume Next` を使っている箇所がありますね。これは、もしカスタムフィールド名が間違っていたり、削除されていたりした場合に、VBAがエラーで止まってしまうのを防ぐためです。業務自動化において、「監視システム自体が原因で業務が止まる」ことは最大の失態です。
# ② `SaveOK = False` の魔法
`ProjectBeforeSave` イベントにおいて、`SaveOK` 引数に `False` を代入すると、Projectは「ユーザーが保存をキャンセルした」と判断し、保存処理を中断します。これにより、不正な状態のままファイルが上書きされるのを物理的に阻止できます。これが「守る」ということです。
# ③ パフォーマンスへの配慮
このロジックは `BeforeSave`(保存直前)にのみ走ります。タスクの入力中に毎回走るわけではないので、ユーザーの操作感を損なうことはありません。「監視の厳格さ」と「ユーザーの利便性」のバランス。これこそが、優れたアーキテクトが常に考えるべきテーマです。
5. 陥りやすい罠と、次へのステップ
このシステムを運用する上で、初心者が陥りやすいミスがいくつかあります。
- 権限の問題: ログファイル(`LOG_FILE_PATH`)を書き込もうとした際、そのフォルダに書き込み権限がないとエラーになります。必ず共有サーバーの適切なディレクトリを指定してください。
- マスターファイルの管理: `MASTER_FORMULA_PATH` 自体が書き換えられてしまったら、監視の意味がありません。このマスターファイルは、VBAで制御できる場所ではなく、読み取り専用の共有フォルダに置くなどの運用ルールが必要です。
最後に:ここをクリアすれば、Project VBAの基本はバッチリですよ
いかがでしたか?
単に「自動化する」だけでなく、「システムとしての信頼性をどう担保するか」という視点を持つこと。これが、マクロの記録から抜け出し、真のエンジニアへと駆け上がるための鍵です。
最初は難しく感じるかもしれません。でも、一つ一つのオブジェクトの動きを理解し、論理的な守りを固めていけば、必ず道は開けます。
次は、このログをExcelに自動集計して、ダッシュボード化する仕組みに挑戦してみるのも面白いですよ。応援しています。共に、極限の自動化を目指しましょう!
