【実務・中級編】【上級者向け】カスタムフィールドの数式構文を外部テキストファイルで管理する仕組み – Project VBA解析バイブル

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

【極限の設計】Project VBAにおけるカスタムフィールド管理の脱・ハードコード戦略

業務自動化の現場において、多くのエンジニアが陥る「致命的な設計ミス」がある。それは、MS Projectのカスタムフィールド(数式)をVBAのソースコード内に直接書き込む(ハードコーディングする)ことだ。

`Task.CustomFields(1).Formula = “[Cost]*0.1″`

一見、これ以上なくシンプルで効率的に見える。しかし、プロジェクトが大規模化し、計算ロジックが頻繁に変更される運用フェーズに入った瞬間、このコードは「技術的負債の塊」へと変貌する。数式を一つ修正するたびに、VBAプロジェクトを書き換え、コンパイルし、配布し直す……。これでは、真の自動化エンジニアとは呼べない。

本稿では、カスタムフィールドの数式を外部テキストファイルで管理し、環境に応じて動的に切り替える「プロフェッショナルな設計手法」を伝授する。これは単なるテクニックではない。「コード(ロジックの実行)」と「データ(ビジネスルール)」を分離するという、ソフトウェア工学における鉄則の実践である。

1. なぜハードコーディングを捨てなければならないのか

設計者として、以下の3つのリスクを直視せよ。

1. 変更コストの増大: 計算式(ビジネスロジック)の変更は、業務要件の変化に伴い頻繁に発生する。コードを触るたびに回帰テストが必要になり、デプロイの心理的・物理的ハードルが上がる。
2. 環境の混在: 開発環境では「テスト用の計算式」を使い、本番環境では「確定した計算式」を使う必要がある。コード内にこれらが混在すると、条件分岐(`If DevMode Then…`)が肥大化し、バグの温床となる。
3. 監査とトレーサビリティの欠如: 数式がVBAのバイナリ(.mppや.xlsm)の中に隠蔽されていると、非エンジニア(PMや監査担当者)が「今、どのような計算ロジックが適用されているか」を確認することが困難になる。

2. アーキテクチャ設計:Formula Engineの概念

目指すべきは、VBAを「数式を読み込み、Projectに注入するだけのエンジン(実行器)」にすることだ。

ファイル構造の定義

設定ファイルは、シンプルかつGit等のバージョン管理システムと相性の良い、キー・バリュー形式のテキストファイルを採用する。

`formulas.conf` (設定ファイルの例)

# Task Custom Fields

Format: FieldIndex|Formula

T_TF1|[Cost]*0.1 T_TF2|IIf([Duration]>60;"Over Limit";"Normal")

Resource Custom Fields

R_TF1|[Unit]*[Rate]

設計のポイント

  • 識別子の設計: `T_TF1`(Taskの1番目のカスタムフィールド)のように、対象オブジェクトとインデックスを明示したキーを用いる。
  • 環境切り替え: 実行時に読み込むファイルパスを、環境変数や実行引数から決定する。

3. 実装:堅牢なFormula Manager

以下に、プロダクション環境で使用することを想定した、エラーハンドリングと柔軟性を兼ね備えたコード例を示す。ここでは、外部ライブラリへの依存を最小限にするため、Late Binding(遅延結合)を活用し、配布時の参照設定エラーを防いでいる。

プロダクション・コード例

Option Explicit

' ==============================================================================
' Module: modFormulaEngine
' Description: カスタムフィールドの数式を外部ファイルから読み込み、Projectに適用する
' ==============================================================================

Public Sub ApplyCustomFieldFormulas(ByVal configFilePath As String)
    Dim fso As Object
    Dim ts As Object
    Dim line As String
    Dim parts() As String
    Dim targetType As String
    Dim fieldIdx As Integer
    Dim formula As String
    
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    ' 1. ファイルの存在チェック(堅牢性の基本)
    If Not fso.FileExists(configFilePath) Then
        Err.Raise vbObjectError + 513, "modFormulaEngine", "設定ファイルが見つかりません: " & configFilePath
    End If
    
    On Error GoTo ErrorHandler
    Set ts = fso.OpenTextFile(configFilePath, 1) ' 1 = ForReading
    
    Do Until ts.AtEndOfStream
        line = Trim(ts.ReadLine)
        
        ' コメント行または空行をスキップ
        If line <> "" And Left(line, 1) <> "#" Then
            ' 区切り文字 "|" で分割
            parts = Split(line, "|")
            If UBound(parts) = 1 Then
                targetType = parts(0) ' T_TF1 or R_TF1
                formula = parts(1)
                
                ' キーの解析
                ' T_ = Task, R_ = Resource
                If Left(targetType, 2) = "T_" Then
                    fieldIdx = ExtractIndex(Mid(targetType, 3))
                    ApplyTaskFormula fieldIdx, formula
                ElseIf Left(targetType, 2) = "R_" Then
                    fieldIdx = ExtractIndex(Mid(targetType, 3))
                    ApplyResourceFormula fieldIdx, formula
                End If
            End If
        End If
    Loop
    
    ts.Close
    MsgBox "カスタムフィールドの更新が完了しました。", vbInformation
    Exit Sub

ErrorHandler:
    If Not ts Is Nothing Then ts.Close
    MsgBox "エラーが発生しました。" & vbCrLf & _
           "Error No: " & Err.Number & vbCrLf & _
           "Description: " & Err.Description, vbCritical, "致命的エラー"
End Sub

' --- 内部補助関数 ---

' 文字列からインデックス番号を抽出
Private Function ExtractIndex(ByVal strIdx As String) As Integer
    ExtractIndex = CInt(strIdx)
End Function

' Taskフィールドへの適用
Private Sub ApplyTaskFormula(ByVal idx As Integer, ByVal formula As String)
    ' MS ProjectのCustomFieldコレクションは、グローバルな定義を書き換える
    ' TaskオブジェクトのCustomFieldsプロパティ経由でアクセス
    On Error Resume Next
    ActiveProject.Tasks.CustomFields(idx).Formula = formula
    If Err.Number <> 0 Then
        Debug.Print "Task Field Error [Idx:" & idx & "]: " & Err.Description
        Err.Clear
    End If
    On Error GoTo 0
End Sub

' Resourceフィールドへの適用
Private Sub ApplyResourceFormula(ByVal idx As Integer, ByVal formula As String)
    On Error Resume Next
    ActiveProject.Resources.CustomFields(idx).Formula = formula
    If Err.Number <> 0 Then
        Debug.Print "Resource Field Error [Idx:" & idx & "]: " & Err.Description
        Err.Clear
    End If
    On Error GoTo 0
End Sub

4. 実務における「極限の注意点」

この設計を導入する際、単にコードをコピーするだけでは不十分だ。以下の3点を設計に組み込め。

① 「数式の構文エラー」という時限爆弾

VBAから `Formula = “…”` を実行した際、もし数式に構文エラー(例:括弧の閉じ忘れ、存在しないフィールド名)が含まれていれば、Projectはランタイムエラーを吐く。
上記のコードでは `On Error Resume Next` を局所的に使用しているが、これは「どの行でエラーが起きたか」をログに残す設計とセットでなければ意味をなさない。実務では、エラーが発生した際に `Debug.Print` だけでなく、エラーログファイルへ書き出す仕組みを検討せよ。

② 環境切り替えのロジック

本番環境と開発環境を切り替える際、以下のような実装を推奨する。

Dim configPath As String
' 環境変数 "PROJECT_ENV" が "PROD" なら本番用、そうでなければ開発用
If Environ("PROJECT_ENV") = "PROD" Then
    configPath = "C:\App\Config\formulas.prod.conf"
Else
    configPath = ThisWorkbook.Path & "\config\formulas.dev.conf"
End If

Call ApplyCustomFieldFormulas(configPath)

これにより、コードを一切書き換えることなく、デプロイメント・パイプライン(CI/CD)に適合させることが可能になる。

③ データの整合性と「Dirty State」

数式を書き換えた際、既存のタスクに残っている「計算結果の値」が古いまま残る場合がある。数式を更新した直後に、必要に応じて `Task.Calculate` や、全タスクをループして再計算を促す処理を挟むのが、プロフェッショナルな振る舞いだ。

結論

優れたエンジニアは、コードを書くことよりも、「変化に強い構造」を作ることに情熱を注ぐ。

カスタムフィールドの数式を外部化することは、単なる手間の削減ではない。それは、ソフトウェアのライフサイクル全体を見据えた、極めて論理的な意思決定である。この設計思想を武器に、メンテナンス地獄から解放された、真に堅牢な自動化ツールを構築せよ。

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