【実務・中級編】【中級者向け】カスタムフィールドの数式にVBA関数を組み込む際の限界と回避策 – Project VBA解析バイブル

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

【極限の設計】Project VBAにおける「ハイブリッド計算」の実装思想 ― カスタムフィールドの限界を超えろ

Microsoft Projectの真価は、標準機能の「カスタムフィールド」にある。しかし、実務の現場で求められるロジックは、Projectが用意した数式(Formula)の檻の中に収まるほど生易しいものではない。

「条件分岐が複雑すぎて数式が読み解けない」
「外部のExcelファイルやデータベースの値を参照して計算したい」
「計算結果に応じて、他のタスクのステータスを連動させたい」

もし君が、これらを無理やり`Formula`プロパティの中に文字列として詰め込もうとしているなら、今すぐ手を止めろ。それは「設計」ではなく、単なる「泥臭い妥協」だ。メンテナンス性が崩壊し、数ヶ月後には誰も触れない、バグの温床となるスパゲッティコードを生むことになる。

本稿では、Projectの数式エンジンの限界を認め、VBAを計算エンジンとして統合する「ハイブリッド計算(Hybrid Calculation)」の設計思想と、プロフェッショナルが実装すべき堅牢なコードパターンを伝授する。

1. なぜ「Formula」では限界が来るのか

Projectのカスタムフィールドに設定する`Formula`は、Project内部の計算エンジンによって評価される。これは非常に高速だが、以下の致命的な制約がある。

1. ロジックのブラックボックス化: 数式が長くなると、デバッグが不可能になる。どの条件で計算が狂ったのか、追跡する手段がない。
2. 外部リソースへのアクセスの欠如: `Formula`はProject内のオブジェクトしか参照できない。外部のAPI、CSV、SQL Serverからのデータ取得は不可能だ。
3. 計算のトリガー制御不能: 数式は「値が変わった時」に再計算されるが、複雑な依存関係を持つ場合、意図しないタイミングで再計算が走り、パフォーマンスを著しく低下させる。

これらを解決するのが、「VBAで計算ロジックを実行し、その結果をカスタムフィールド(Text型やNumber型)に直接書き出す」というハイブリッドアプローチだ。

2. ハイブリッド計算の設計原則

単に「VBAでループを回して値を書き込む」だけでは、中級者の域を出ない。アーキテクトとして意識すべきは、「計算のライフサイクル」と「データの整合性」だ。

設計思想:Push型計算モデル

Projectの数式エンジンに計算を「任せる(Pull)」のではなく、VBA側から計算結果を「送り込む(Push)」設計にする。

  • 計算エンジン(VBA): ロジック、外部データ連携、エラーハンドリングを担う。
  • データ格納領域(Custom Fields): 計算結果を保持するだけの「純粋な器」として扱う。数式(Formula)は空にしておく。

これにより、「計算ロジック(VBA)」と「表示データ(Field)」が分離され、デバッグが劇的に容易になる。

3. プロダクション・グレードの実装例

ここでは、具体的なユースケースとして「タスクの期間と外部のリスク係数を掛け合わせ、プロジェクトの『重要度スコア』を算出する」ロジックを実装する。

以下のコードは、単なる計算スクリプトではない。エラーハンドリング、外部連携の抽象化、そしてパフォーマンスを考慮した設計になっている。

Option Explicit

' ==============================================================================
' Module: modCalculationEngine
' Description: プロジェクトの重要度スコアを算出するハイブリッド計算エンジン
' ==============================================================================

''' <summary>
''' 全タスクに対して重要度スコアを再計算し、カスタムフィールドに書き込むメインルーチン
''' </summary>
Public Sub ExecuteImportanceScoreCalculation()
    Dim t As Task
    Dim score As Double
    Dim riskFactor As Double
    Dim errorCount As Long
    
    ' パフォーマンス最適化: 画面更新と再計算の抑制(Project特有の挙動に注意)
    ' ※ ProjectにはExcelのようなScreenUpdatingはないが、
    '   大規模な場合は計算モードの制御を検討する
    
    On Error GoTo ErrorHandler
    
    Debug.Print "--- Calculation Started: " & Now & " ---"
    
    ' プロジェクト内の全タスクを走査
    For Each t In ActiveProject.Tasks
        ' サマリータスクや削除済みタスクをスキップ
        If Not t Is Nothing Then
            If Not t.Summary Then
                
                ' 1. 外部データの取得(抽象化されたロジック)
                riskFactor = GetExternalRiskFactor(t.Name)
                
                ' 2. 複雑な計算ロジックの実行
                score = CalculateComplexScore(t, riskFactor)
                
                ' 3. 結果の書き出し (Number1 フィールドをスコア格納用に使用)
                ' Formulaプロパティは空である前提
                t.Number1 = score
                
            End If
        End If
    Next t
    
    MsgBox "計算が完了しました。", vbInformation, "Success"
    Exit Sub

ErrorHandler:
    errorCount = errorCount + 1
    Debug.Print "Error on Task: " & IIf(t Is Nothing, "Unknown", t.Name) & " | " & Err.Description
    Resume Next
End Sub

''' <summary>
''' 複雑なスコアリングロジックをカプセル化
''' </summary>
''' <param name="targetTask">対象タスク</param>
''' <param name="risk">外部から取得したリスク係数</param>
''' <returns>算出されたスコア</returns>
Private Function CalculateComplexScore(targetTask As Task, risk As Double) As Double
    Dim baseScore As Double
    
    ' 例: 期間(Days) × リソースコスト × リスク係数
    ' ここにVBAなら容易に書ける複雑なIF文や数学関数を投入する
    baseScore = targetTask.Duration / 480 ' 1日=480分換算
    
    If targetTask.Priority > 500 Then
        baseScore = baseScore * 1.5
    End If
    
    ' 最終的なスコア算出
    CalculateComplexScore = baseScore * risk
End Function

''' <summary>
''' 外部リソース(CSVやDB等)からリスク係数を取得する
' ※ 実務ではここをAPIやSQL接続に置き換える
''' </summary>
Private Function GetExternalRiskFactor(taskName As String) As Double
    ' 本来はここで外部ファイルやDBを検索する
    ' 今回はデモ用に、タスク名に "Critical" が含まれていれば高リスクとする
    If InStr(1, taskName, "Critical", vbTextCompare) > 0 Then
        GetExternalRiskFactor = 2.0
    Else
        GetExternalRiskFactor = 1.0
    End If
End Function

4. アーキテクトが教える「落とし穴」と回避策

この設計を導入する際、君が絶対に避けるべき「設計の負債」が3つある。

① ループ内での「重い操作」

`For Each t In ActiveProject.Tasks` のループ内で、毎回 `FileSystemObject` を生成したり、SQLの `Connection` を開閉したりしてはいけない。

  • 正解: ループの外でリソース(ファイルハンドル、DB接続)を一度だけ開き、ループ内ではその参照を使い回すこと。

② 依存関係の連鎖による無限ループ

あるタスクの計算結果を書き込むことで、Projectの自動計算機能が走り、それがまたVBAのイベント(`Project_Calculate`等)をトリガーする……という設計は、プロジェクトをフリーズさせる。

  • 正解: 計算は「イベント駆動(自動)」ではなく、「コマンド駆動(手動または特定のタイミング)」で行う設計にせよ。ユーザーが明示的に「再計算ボタン」を押す、あるいは特定の保存タイミングで行うのが最も堅牢だ。

③ データの「型」の不一致

`Number1` フィールドに浮動小数点を書き込む際、表示形式の設定によっては、丸め誤差によってユーザーが混乱することがある。

  • 正解: 計算結果の精度(小数点以下何桁か)を明確に定義し、必要であれば `Format` 関数等で整形した結果を `Text` フィールドに書き出すという使い分けを検討せよ。

結論

Project VBAにおける真の自動化とは、Projectの機能に頼ることではなく、Projectを「高度なデータ構造を持つUI」として扱い、ロジックを「自前のエンジン」に切り離すことにある。

「数式で書けるか?」と自問するのをやめ、「このロジックをどう設計すれば、半年後の自分(あるいは後任)が迷わずに修正できるか?」を考えろ。それが、プロフェッショナルなエンジニアへの第一歩だ。

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