【実務・中級編】【上級者向け】カスタムフィールドの数式エラーを自動検知して修復する自己修復ツール – Project VBA解析バイブル

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

脆弱な自動化を卒業せよ: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.

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