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

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

エントロピーへの抗い:MS Projectにおけるカスタムフィールド「自己修復型」アーキテクチャの構築

大規模なプロジェクト管理環境において、Microsoft Projectは極めて強力なツールである。しかし、その強力さの裏側には、ユーザーによる誤操作や、複雑な数式(Formula)の依存関係が引き起こす「計算エンジンの崩壊」という致命的な脆弱性が潜んでいる。

カスタムフィールドに設定された数式が、何らかの理由で不正な参照(例:存在しないフィールドへの参照や、循環参照によるエラー)に陥った際、MS Projectは静かに、しかし確実にそのフィールドの計算を停止させる。これは単なるデータの欠落ではない。プロジェクト全体のクリティカルパス計算やリソース平準化のロジックを根底から狂わせる「静かなる破壊」である。

本稿では、シニアエンジニアが直面するこの問題に対し、単なるエラーハンドリングを超えた、「スキーマ駆動型の自己修復メカニズム」の構築手法を詳説する。

1. 凡庸なエラーハンドリングの限界

多くの開発者は、`On Error GoTo` を用いてエラーを捕捉しようとする。しかし、それは致命的な誤解だ。

カスタムフィールドの数式エラーは、VBAのランタイムエラーではない。それはMS Projectの内部計算エンジン(Calculation Engine)内で発生する論理エラーである。VBAからフィールドの値を参照しようとした際、エラー値が返ってくることはあっても、VBAの実行フロー自体が中断されるわけではない。

我々に求められるのは、実行時のエラー捕捉ではなく、「あるべき状態(Desired State)」と「現在の状態(Current State)」の差分を検出し、強制的に同期させるという、インフラストラクチャ・アズ・コード(IaC)に近い思想である。

2. アーキテクチャ設計:スキーマ駆動型修復モデル

堅牢な自己修復ツールを構築するためには、以下の3つのレイヤーを分離して設計する必要がある。

1. Definition Schema (定義層): 正しいフィールド名、数式、データ型を保持する「真実の源(Source of Truth)」。
2. Detection Engine (検知層): 現在のプロジェクト内のフィールド状態を走査し、スキーマとの不一致を特定する。
3. Actuator (実行層): 不一致を検知した際、APIを通じて数式を再注入し、計算エンジンを再起動させる。

メモリ管理とオブジェクトのライフサイクル

大量のタスクやリソースを扱う際、VBAにおけるオブジェクトの扱いは極めて慎重に行わなければならない。`Task` オブジェクトや `Resource` オブジェクトをループ内で大量に生成・参照すると、COM(Component Object Model)の参照カウントが適切に管理されず、MS Projectのメモリ使用量が指数関数的に増大し、最終的にプロセスがクラッシュする。

必ず、明示的なオブジェクトの解放(`Set obj = Nothing`)と、大規模ループにおけるメモリの断片化防止を意識した実装を行う。

3. 実装:Self-Healing Custom Field Manager

以下に、Taskフィールドの数式を検証し、破損していれば自動修復するプロフェッショナルなクラスモジュールの設計例を示す。

Schema Definition (Dictionaryによる高速検索)

まず、正しい数式を管理するための構造を定義する。

' --- Module: msd_Constants ---
Public Enum FieldType
    ftText = 1
    ftNumber = 2
    ftDate = 3
End Enum

' --- Class: clsFieldSchema ---
' フィールドの「正解」を定義するコンテナ
Public FieldName As String      ' 例: "Text1"
Public CorrectFormula As String  ' 例: "[Duration]/[Work]"
Public ExpectedType As FieldType

Core Engine (修復ロジック)

' --- Class: clsFieldRepairer ---
Option Explicit

Private pApp As MSProject.Application
Private pSchema As Collection ' clsFieldSchemaのコレクション

Private Sub Class_Initialize()
    Set pApp = MSProject.Application
    Set pSchema = New Collection
End Sub

' スキーマの登録
Public Sub RegisterSchema(ByRef schema As clsFieldSchema)
    pSchema.Add schema, schema.FieldName
End Sub

' 自己修復プロセスの実行
Public Sub ExecuteRepair()
    Dim schemaItem As Variant
    Dim targetField As Object
    Dim isCorrupted As Boolean
    Dim repairCount As Long
    
    repairCount = 0
    
    ' Windows APIを使用して、計算処理中のUIフリーズを防ぐための準備(後述)
    ' 実際にはDoEventsを戦略的に配置する
    
    On Error Resume Next ' 内部的なプロパティアクセスエラーを許容
    
    For Each schemaItem In pSchema
        ' MS ProjectのCustomFieldオブジェクトへアクセス
        ' Note: MS Projectのオブジェクトモデルは、フィールド名で直接アクセスするより
        ' 内部的なIDやインデックスを利用する方がパフォーマンスが高い場合がある
        
        Set targetField = pApp.CustomFields(schemaItem.FieldName)
        
        If targetField Is Nothing Then
            ' フィールド自体が存在しない致命的なケース
            Debug.Print "CRITICAL: Field " & schemaItem.FieldName & " is missing."
            GoTo NextSchema
        End If

        ' 1. 数式の整合性チェック
        ' 現在の数式がスキーマと一致しているか
        If targetField.Formula <> schemaItem.CorrectFormula Then
            isCorrupted = True
        Else
            ' 2. 計算結果の整合性チェック
            ' 数式が正しくても、計算結果がエラー(#ERROR!)を返していないか確認
            ' ここでは、最初のタスクの値をサンプリングしてチェックする手法をとる
            If CheckForCalculationError(schemaItem.FieldName) Then
                isCorrupted = True
            End If
        End If

        ' 修復実行
        If isCorrupted Then
            Debug.Print "Repairing field: " & schemaItem.FieldName
            targetField.Formula = schemaItem.CorrectFormula
            repairCount = repairCount + 1
            
            ' 修復直後に計算エンジンを強制更新
            pApp.Calculate
        End If

NextSchema:
    Next schemaItem

    Debug.Print "Repair completed. Total fields repaired: " & repairCount
    
    ' オブジェクトの明示的解放
    Set pSchema = Nothing
    Set pApp = Nothing
End Sub

' 計算エラーの検知(サンプリング手法)
Private Function CheckForCalculationError(ByVal fieldName As String) As Boolean
    Dim t As MSProject.Task
    Dim val As Variant
    
    ' 全タスクを走査するのは重いため、先頭の数件をサンプリング、
    ' または特定のフラグが立っているタスクのみを対象とするのがプロの設計
    For Each t In pApp.ActiveProject.Tasks
        If Not t Is Nothing Then
            val = t.get_CustomField(fieldName) ' 実行時エラーを避けるためget_CustomFieldを使用
            
            ' MS Projectのエラー値(文字列としての"#ERROR!"など)を検知
            If IsError(val) Or val = "#ERROR!" Or val = "" Then
                CheckForCalculationError = True
                Exit Function
            End If
        End If
        
        ' 大規模プロジェクトでの無限ループ・フリーズ防止
        DoEvents 
    Next t
    
    CheckForCalculationError = False
End Function

4. Windows APIによる低レイヤーの制御

大規模な修復処理を行う際、MS Projectは「応答なし」状態になりやすい。これを回避し、ユーザーにプロセスの進行を意識させるために、Windows APIを用いてメッセージキューを制御する。

' --- Module: msd_WinAPI ---
Option Explicit

If VBA7 Then

Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr) Public Declare PtrSafe Function GetTickCount Lib "kernel32" () As LongPtr

Else

Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) Public Declare Function GetTickCount Lib "kernel32" () As Long

End If

' プロフェッショナルな実装では、単なるDoEventsではなく、 ' 特定のインターバル(例:100ms)ごとにメッセージを処理させることで、 ' CPU負荷を抑えつつ、UIの応答性を確保する。

5. 結論:レガシー資産を維持する「守護者」の思考

カスタムフィールドの自動修復ツールを構築することは、単なる「コードの自動化」ではない。それは、不完全な人間が操作するレガシーなシステムに対して、「数学的な正当性」を強制的に再注入するプロセスである。

シニアエンジニアが作るべきは、エラーが起きないシステムではない。エラーが起きた瞬間に、システムが自律的にその不整合を検知し、設計図(Schema)に基づいた正しい状態へと回帰するシステムである。

この「自己修復」の概念を、カスタムフィールドだけでなく、リソース配分、コスト計算、あるいは外部データベースとの連携ロジックにまで拡張していくこと。それが、アーキテクトとしての真の到達点である。

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