【実務・中級編】【プロ向け】計算エンジンが「矛盾」を検知した際のVBAハンドリング:Project.Task.ConstraintDateの検証アルゴリズム – Project VBA解析バイブル

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

【極限の知見】MS Projectの計算エンジンを支配せよ:制約条件(ConstraintDate)の矛盾を検知する堅牢な検証アルゴリズム

MS Projectを単なる「ガントチャートを描くツール」だと思っているなら、君の自動化エンジニアとしてのキャリアはそこで止まる。

プロフェッショナルが扱うMS Projectは、複雑な依存関係とリソース制約が絡み合う「動的な計算エンジン」だ。ユーザーが「Must Start On(開始日固定)」などの制約条件を不用意に設定したとき、計算エンジンは沈黙の中で、論理的に破綻したスケジュールを生成することがある。

VBAでツールを開発する際、最も恐ろしいのは「エラーで止まること」ではない。「エラーが出ずに、間違ったスケジュールを正しいものとして出力してしまうこと」だ。

本記事では、計算エンジンが陥る「制約条件の矛盾」を論理的に特定し、どのタスクがスケジュールの整合性を破壊しているかを正確にトラップする、プロダクションレベルの検証アルゴリズムを伝授する。

1. なぜ「ConstraintType」のチェックだけでは不十分なのか

多くの初級者は、タスクの制約をチェックする際に `Task.ConstraintType` を参照するだけで満足する。しかし、それは設計思想として極めて浅い。

`ConstraintType` が `pjMustStartOn` であることは単なる「状態」に過ぎない。真に検証すべきは、「その制約が、先行・後続タスクとの論理的関係(Relationship)と衝突していないか」という「論理的整合性(Logical Integrity)」だ。

矛盾が発生するメカニズム

1. 制約の強制: ユーザーがタスクAに「Must Start On(2023/10/01)」を設定する。
2. 論理の衝突: タスクAの先行タスクBが「2023/10/05」に終了する設定になっている。
3. エンジンの挙動: MS Projectの計算エンジンは、制約を優先しようとするが、先行関係との間に「負のラグ(Negative Lag)」や物理的な不可能性が発生する。このとき、スケジュールは「計算上は成立しているが、実務上は破綻している」状態に陥る。

これをVBAで検知するには、単一タスクのプロパティを見るのではなく、タスクの「制約日付」と「ネットワーク上の日付」を比較するアルゴリズムが必要不可欠となる。

2. 設計思想:堅牢な検証エンジンのアーキテクチャ

業務ツールとして提供する場合、検証ロジックは以下の3層構造で設計すべきである。

1. Data Acquisition Layer (収集層): `Project.Task` オブジェクトから、制約型、制約日付、計算後の開始/終了日、および先行・後続関係を高速に抽出する。
2. Logic Validation Layer (検証層): 抽出したデータを、あらかじめ定義された「矛盾パターン」と照合する。
3. Reporting Layer (報告層): 矛盾を「エラーオブジェクト」としてカプセル化し、ログファイルやデータベースへ書き出す。

ここで重要なのは、「計算エンジンをループ内で何度も再計算(Calculate)させない」ことだ。大規模プロジェクトにおいて `Project.Calculate` をループ内で呼び出すことは、パフォーマンスに対する自殺行為である。

3. 実装例:Constraint Violation Detector

以下に、制約条件の矛盾を検知するための、クラスモジュールを活用した堅牢なコード例を示す。

クラスモジュール: `clsViolation`

矛盾の内容を保持するための専用クラスだ。単なる文字列ではなく、オブジェクトとして扱うことで、後のレポート出力(ExcelやSQLへの書き出し)が劇的に容易になる。

' Class Module: clsViolation
Option Explicit

Public TaskID As Long
Public TaskName As String
Public ConstraintType As Long
Public ConstraintDate As Date
Public ActualStartDate As Date
Public ConflictType As String ' "Constraint vs Predecessor" 等
Public Severity As String     ' "Critical" or "Warning"

' 矛盾内容を整形して返すプロパティ
Public Property Get Description() As String
    Description = "[" & TaskID & "] " & TaskName & ": " & ConflictType & _
                  " (Constraint: " & ConstraintDate & " vs Calculated: " & ActualStartDate & ")"
End Property

標準モジュール: `modScheduleValidator`

メインのロジック。計算負荷を最小限に抑えつつ、論理的矛盾をあぶり出す。

Option Explicit

' 矛盾の種類を定義
Public Enum ViolationSeverity
    svWarning = 1
    svCritical = 2
End Enum

''' <summary>
''' プロジェクト全体の制約条件の矛盾を検証する
''' </summary>
Public Function ValidateConstraints(ByRef pj As MSProject.Project) As Collection
    Dim violations As New Collection
    Dim t As MSProject.Task
    Dim pred As MSProject.Task
    
    ' 計算エンジンが最新の状態であることを前提とする
    ' ループ内で Calculate は絶対に呼ばないこと
    
    For Each t In pj.Tasks
        ' 有効なタスク(削除済みでない)かつ制約があるものに絞る
        If Not t Is Nothing Then
            If Not t.ExternalTask And t.ConstraintType <> pjConstraintNone Then
                
                ' --- パターン1: Must Start On / Must Finish On の整合性チェック ---
                CheckConstraintConsistency t, violations
                
                ' --- パターン2: 先行タスクとの論理的衝突チェック ---
                CheckPredecessorConflict t, violations
                
            End If
        End If
    Next t
    
    Set ValidateConstraints = violations
End Function

''' <summary>
''' 制約日付と計算された日付の乖離をチェック
''' </summary>
Private Sub CheckConstraintConsistency(ByRef t As MSProject.Task, ByRef col As Collection)
    Dim v As clsViolation
    
    Select Case t.ConstraintType
        Case pjMustStartOn
            If Abs(t.ConstraintDate - t.Start) > 0.5 Then ' 0.5日は半日の誤差を許容する場合
                Set v = New clsViolation
                With v
                    .TaskID = t.ID
                    .TaskName = t.Name
                    .ConstraintType = t.ConstraintType
                    .ConstraintDate = t.ConstraintDate
                    .ActualStartDate = t.Start
                    .ConflictType = "Constraint Date Mismatch"
                    .Severity = "Critical"
                End With
                col.Add v
            End If
            
        Case pjMustFinishOn
            If Abs(t.ConstraintDate - t.Finish) > 0.5 Then
                Set v = New clsViolation
                With v
                    .TaskID = t.ID
                    .TaskName = t.Name
                    .ConstraintType = t.ConstraintType
                    .ConstraintDate = t.ConstraintDate
                    .ActualStartDate = t.Finish
                    .ConflictType = "Constraint Date Mismatch"
                    .Severity = "Critical"
                End With
                col.Add v
            End If
    End Select
End Sub

''' <summary>
''' 先行タスクの終了日と、タスクの制約開始日の衝突をチェック
''' </summary>
Private Sub CheckPredecessorConflict(ByRef t As MSProject.Task, ByRef col As Collection)
    Dim pred As MSProject.Task
    Dim v As clsViolation
    
    ' 先行関係を走査
    ' 注意: t.Predecessors は依存関係のオブジェクトを返す
    For Each pred In t.Predecessors
        ' 制約が「開始日固定」かつ、先行タスクの終了日よりも前の日付である場合
        If (t.ConstraintType = pjMustStartOn Or t.ConstraintType = pjStartNoEarlierThan) Then
            If t.ConstraintDate < pred.Finish Then
                Set v = New clsViolation
                With v
                    .TaskID = t.ID
                    .TaskName = t.Name
                    .ConstraintType = t.ConstraintType
                    .ConstraintDate = t.ConstraintDate
                    .ActualStartDate = t.Start
                    .ConflictType = "Conflict with Predecessor [" & pred.ID & "]"
                    .Severity = "Critical"
                End With
                col.Add v
            End If
        End If
    Next pred
End Sub

4. プロダクション実装における極意:外部連携とエラーハンドリング

このバリデーションエンジンを実務ツールに組み込む際、エンジニアが陥りやすいミスが2点ある。

① ログの永続化(File/DB連携)

検証結果をVBAのコンソール(Immediate Window)に表示して終わらせるのは素人だ。
実務では、「誰が、いつ、どのプロジェクトで、どの矛盾を発生させたか」を記録する必要がある。

  • 推奨アプローチ: `clsViolation` のコレクションをループし、CSV形式またはSQLite等の軽量DBへ一括インサートする。
  • 注意点: 大規模プロジェクトでは、エラー件数が数百件に及ぶことがある。一回ずつファイルを開閉するのではなく、`FileSystemObject` を用いてバッファリングするか、一括書き出しを行う設計にせよ。

② 実行時エラーのトラップ

MS Projectのオブジェクトモデルは、タスクが削除された直後や、プロジェクトが読み込み中の不安定な状態でアクセスすると、予期せぬ `Run-time error ‘424’: Object required` を吐き出す。

' 呼び出し側の堅牢な実装例
Public Sub RunAudit()
    On Error GoTo ErrorHandler
    Dim violations As Collection
    Set violations = ValidateConstraints(Application.Project)
    
    If violations.Count > 0 Then
        ' ここでExcelへの出力やDBへの保存を行う
        Debug.Print "Audit Complete: " & violations.Count & " issues found."
    Else
        Debug.Print "Audit Complete: No issues detected."
    End If
    
    Exit Sub

ErrorHandler:
    ' 致命的なエラー(オブジェクトの消失など)を記録
    MsgBox "Critical Error during Audit: " & Err.Description, vbCritical
    ' ここでエラーログを外部ファイルに吐き出す処理を挟むのがプロの仕事だ
End Sub

結語:エンジニアとしての矜持

MS Projectの自動化において、コードが「動く」ことは最低条件に過ぎない。
真に価値のあるツールとは、「計算エンジンの背後にある論理的矛盾を、人間の目以上に鋭敏に検知し、ユーザーに警告を与えるもの」である。

制約条件の検証アルゴリズムを実装することは、単なるデバッグ作業ではない。それは、プロジェクトの「時間軸の整合性」を守るための、アーキテクトとしての防衛策なのだ。

この知見を武器に、単なる作業の自動化を超え、意思決定を支える堅牢なシステムを構築してほしい。

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