【極限の知見】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の自動化において、コードが「動く」ことは最低条件に過ぎない。
真に価値のあるツールとは、「計算エンジンの背後にある論理的矛盾を、人間の目以上に鋭敏に検知し、ユーザーに警告を与えるもの」である。
制約条件の検証アルゴリズムを実装することは、単なるデバッグ作業ではない。それは、プロジェクトの「時間軸の整合性」を守るための、アーキテクトとしての防衛策なのだ。
この知見を武器に、単なる作業の自動化を超え、意思決定を支える堅牢なシステムを構築してほしい。
