脆弱な自動化を卒業せよ:MS Project VBAにおける「自己修復型」カスタムフィールド管理アーキテクチャ
自動化エンジニアとしてプロジェクトを完遂したとき、多くの者は「動くものを作った」と満足する。しかし、真のアーキテクトは、そのツールが「ユーザーの誤操作やテンプレートの変質によって壊れること」を前提に設計する。
Microsoft Projectにおけるカスタムフィールド(Task/Resource Custom Fields)は、その極めて高い利便性と引き換えに、恐ろしく「脆い(Brittle)」性質を持っている。数式のわずかな書き換え、フィールド型の不一致、あるいはテンプレートのインポートに伴う定義の不整合。これらは、昨日まで完璧に動作していたレポートを、一夜にして「#ERROR!」の地獄へと突き落とす。
本記事では、単なる「数式の設定スクリプト」ではない。壊れた状態を検知し、定義(Source of Truth)に基づいて自動的に復元する、「自己修復(Self-Healing)」メカニズムの構築手法を伝授する。
1. なぜ、あなたのカスタムフィールドは壊れるのか
まず、敵を知ることだ。なぜカスタムフィールドの数式は、メンテナンスフェーズで破綻するのか。
1. 定義の競合(Schema Mismatch): 別のテンプレートからフィールドをコピーした際、数式は引き継がれても、フィールドのデータ型(Text, Number, Date等)が意図せず上書きされる。
2. 依存関係の断絶: 数式内で参照している特定のカスタムフィールドが削除されたり、名前が変更されたりすることで、数式エンジンが解析不能に陥る。
3. ユーザーによる「汚染」: ユーザーが数式フィールドに対して、直接手入力を行おうとする(あるいは、数式がテキスト型であるために誤って値を書き換える)ことで、内部的な計算ロジックが破壊される。
これらを「人間が気づいて直す」という運用設計は、プロフェッショナルの仕事ではない。「コードが自律的に検知し、正解へと書き戻す」。これが、堅牢なシステムにおける唯一の回答である。
2. 設計思想:Declarative Configuration(宣言型定義)への移行
多くの初級者が犯すミスは、VBAコードの中に直接 `If field.Name = “Cost_Analysis” Then field.Formula = “…”` とハードコーディングすることだ。これは保守性の観点から見て「最悪」の設計である。
我々が採用すべきは、「現在の状態(Current State)」と「あるべき姿(Desired State)」を比較するアーキテクチャだ。
理想的なパイプライン
1. Definition Layer: 正しい数式と型を定義した「マスターデータ(JSON, CSV, または専用の定数クラス)」。
2. Audit Engine: プロジェクト内の全カスタムフィールドを走査し、マスターデータとの差異(数式の文字列、データ型、使用フラグ)を抽出する。
3. Repair Engine: 差異が検知されたフィールドに対してのみ、原子的な(Atomicな)修正操作を実行する。
4. Verification Layer: 修正後に再度Auditを行い、完全な一致を確認する。
3. 実装:自己修復型メンテナンス・エンジンの構築
以下に、プロダクション環境に投入可能なレベルのVBA実装例を示す。ここでは、メンテナンス性を高めるために、カスタムフィールドの定義を構造体(Type)として管理し、それをスキャンするロジックを組んでいる。
Option Explicit
' ==============================================================================
' Custom Field Definition Structure
' カスタムフィールドの「あるべき姿」を定義するための構造体
' ==============================================================================
Private Type FieldDefinition
FieldName As String ' フィールド名
Formula As String ' 正解の数式
FieldType As Long ' プロジェクトの定数 (pjTaskText, pjTaskNumber, etc.)
IsTaskField As Boolean ' TaskフィールドかResourceフィールドか
End Type
' ==============================================================================
' Main Entry Point: Audit and Repair
' プロジェクト全体のカスタムフィールドを診断し、修復する
' ==============================================================================
Public Sub ExecuteSelfHealingEngine()
Dim masterDefinitions() As FieldDefinition
Dim auditResult As Boolean
On Error GoTo ErrorHandler
' 1. マスター定義のロード (本来は外部ファイルやDBから読み込むべき)
masterDefinitions = LoadMasterDefinitions()
Debug.Print "--- Self-Healing Process Started at " & Now & " ---"
' 2. 診断と修復の実行
auditResult = RunAuditAndRepair(masterDefinitions)
' 3. 結果報告
If auditResult Then
MsgBox "診断完了: すべてのカスタムフィールドは正常な状態に維持されています。", vbInformation, "System Healthy"
Else
MsgBox "警告: 一部のフィールドに異常を検知しましたが、自動修復を試みました。ログを確認してください。", vbExclamation, "Repair Action Taken"
End If
Debug.Print "--- Process Completed ---"
Exit Sub
ErrorHandler:
MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical, "Engine Failure"
End Sub
' ==============================================================================
' Audit Engine: 差異の検知と修復のコアロジック
' ==============================================================================
Private Function RunAuditAndRepair(ByRef definitions() As FieldDefinition) As Boolean
Dim i As Long
Dim def As FieldDefinition
Dim currentField As Object ' MS Project Object Model (TaskCustomField / ResourceCustomField)
Dim isRepaired As Boolean
Dim foundMismatch As Boolean
isRepaired = False
foundMismatch = False
For i = LBound(definitions) To UBound(definitions)
def = definitions(i)
' 対象のコレクションを選択 (Task または Resource)
If def.IsTaskField Then
Set currentField = GetTaskCustomField(def.FieldName)
Else
Set currentField = GetResourceCustomField(def.FieldName)
End If
' フィールドが存在しない場合のハンドリング
If currentField Is Nothing Then
Debug.Print "[CRITICAL] Field Not Found: " & def.FieldName
' ここでは「存在しない」ことをエラーとし、必要ならフィールド作成ロジックを呼ぶ
GoTo NextIteration
End If
' --- 診断フェーズ (Audit) ---
' 1. 数式の一致確認
' 2. データ型の一致確認 (重要: 型が違うと数式エラーの原因になる)
If currentField.Formula <> def.Formula Or currentField.Type <> def.FieldType Then
foundMismatch = True
Debug.Print "[MISMATCH] Field: " & def.FieldName & " | Expected: " & def.Formula & " | Actual: " & currentField.Formula
' --- 修復フェーズ (Repair) ---
On Error Resume Next
currentField.Type = def.FieldType
currentField.Formula = def.Formula
If Err.Number <> 0 Then
Debug.Print "[ERROR] Failed to repair " & def.FieldName & ": " & Err.Description
Err.Clear
Else
Debug.Print "[SUCCESS] Repaired: " & def.FieldName
isRepaired = True
End If
On Error GoTo 0
End If
NextIteration:
Next i
RunAuditAndRepair = isRepaired
End Function
' ==============================================================================
' Helper Functions: MS Project Object Model へのアクセス
' ==============================================================================
Private Function GetTaskCustomField(ByVal name As String) As Object
Dim tf As TaskCustomField
' プロジェクト内の全カスタムフィールドを走査 (パフォーマンスを考慮する場合、インデックス管理を推奨)
For Each tf In ActiveProject.TaskCustomFields
If tf.Name = name Then
Set GetTaskCustomField = tf
Exit Function
End If
Next tf
Set GetTaskCustomField = Nothing
End Function
Private Function GetResourceCustomField(ByVal name As String) As Object
Dim rf As ResourceCustomField
For Each rf In ActiveProject.ResourceCustomFields
If rf.Name = name Then
Set GetResourceCustomField = rf
Exit Function
End If
Next rf
Set GetResourceCustomField = Nothing
End Function
' ==============================================================================
' Master Data: Source of Truth
' プロダクションでは、このデータを外部(JSON/SQL/Excel)からロードする設計にせよ
' ==============================================================================
Private Function LoadMasterDefinitions() As FieldDefinition()
Dim defs(0 To 2) As FieldDefinition
' 例1: Task Text Field
defs(0).FieldName = "Project_Phase"
defs(0).Formula = "Text1" ' 単純な参照例
defs(0).FieldType = pjTaskText
defs(0).IsTaskField = True
' 例2: Task Number Field (複雑な計算式)
defs(1).FieldName = "Efficiency_Score"
defs(1).Formula = "[Cost]/[Work]*100"
defs(1).FieldType = pjTaskNumber
defs(1).IsTaskField = True
' 例3: Resource Text Field
defs(2).FieldName = "Skill_Level"
defs(2).Formula = "Text1"
defs(2).FieldType = pjResourceText
defs(2).IsTaskField = False
LoadMasterDefinitions = defs
End Function
4. プロフェッショナルのための高度なアドバイス
このコードを実務に投入する際、以下の3点を必ず考慮せよ。
① 外部マスター管理の徹底
上記のコードでは `LoadMasterDefinitions` 内にハードコーディングしているが、これはあくまでデモだ。実務では、「数式だけを管理するExcelファイル」や「JSONファイル」をマスターとして用意せよ。これにより、数式を変更する際にVBAコードを書き換える(=コンパイル・再配布する)必要がなくなり、ビジネスロジックの変更とシステムの更新を分離できる。
② データベース連携時のトランザクション思考
もしこのツールを大規模なエンタープライズ環境で使用し、修復結果をSQL Serverなどの外部DBにログとして書き込む場合、「部分的な修復」に注意せよ。10個のフィールドのうち5個だけ直してエラーで止まった場合、プロジェクトの状態は「中途半端な不整合」を抱えることになる。修復処理全体を、Try-Catch的な構造で包み込み、失敗時にはログを詳細に残す設計にせよ。
③ パフォーマンスの重み
`For Each tf In ActiveProject.TaskCustomFields` は、カスタムフィールドの数が増えると指数関数的に重くなる。大規模なプロジェクトを扱うなら、フィールド名とインデックスの「マッピングテーブル」をメモリ上にキャッシュしておくことで、検索コストを $O(n)$ から $O(1)$ に近づけることが可能だ。
結論
「動けばいい」という考え方は、自動化エンジニアの成長を止める。
「壊れることを想定し、自律的に回復する仕組み」を組み込むこと。これこそが、単なるスクリプターと、伝説的なアーキテクトを分かつ境界線である。
Build robust. Build to last.
