Project VBAを掌握する極限の知見:依存関係と制約条件の「論理矛盾」を検知する整合性チェックエンジン
こんにちは。現場のコードを愛し、複雑なプロジェクトの混沌をロジックで切り拓くエンジニアの皆さん。
Microsoft Project(以下、MS Project)を使いこなしていると、ある「壁」にぶつかることがあります。それは、「入力したデータが、プロジェクトの論理として正しいのかどうかが、見た目だけでは分からない」という問題です。
「タスクAが終わったらタスクBを始める(FS関係)」と設定しているのに、タスクBに「〇月〇日に必ず開始する(Must Start On)」という制約を無理やり入れたとき。MS Projectはエラーを出して止まってくれるわけではありません。ただ、スケジュールが不自然に引き延ばされたり、あるいは制約が無視されたような計算結果を表示したりします。
これが積み重なると、スケジュールは「嘘」をつき始めます。
今日は、マクロの記録レベルを脱却し、プロジェクトの「脳」であるスケジュール計算エンジンをコントロールするための第一歩として、「依存関係(Link)と制約条件(Constraint)の矛盾を検知する整合性チェックエンジン」の作り方を伝授しましょう。
1. なぜ「矛盾」が生まれるのか? ―― オブジェクトの二重構造を理解する
まず、プロとして知っておかなければならないのは、MS Projectのタスクが持つ「二つの日付」の概念です。
1. 計算された日付 (Calculated Date): 前後のタスクとの依存関係(FS, SS等)に基づいて、計算エンジンが導き出した日付。
2. 制約日付 (Constraint Date): ユーザーが「この日以降に開始」や「この日に必ず開始」と強制的に指定した日付。
エンジニアリングの観点で見れば、これは「動的な制約(依存関係)」と「静的な制約(制約条件)」の衝突です。
矛盾の典型パターン
- パターンA(遅延の強制): 前のタスクが10月10日に終わるのに、次のタスクに「10月5日までに開始」という制約を入れた場合。
- パターンB(不自然な空き): 前のタスクが10月1日に終わるのに、次のタスクに「10月20日以降に開始」という制約を入れた場合(これは論理的には成立しますが、スケジュール管理上は「予期せぬ待ち時間」というリスクになります)。
これらを自動で検知できれば、プロジェクトマネージャーに「データの整合性が崩れていますよ」と即座に警告できる、プロフェッショナルなツールになります。
2. アルゴリズムの設計図
単純なループ処理で終わらせず、拡張性を持たせた設計を考えましょう。今回は、全タスクを走査し、制約条件の種類(`ConstraintType`)を判別して、計算された日付との乖離をチェックするロジックを構築します。
検知のロジックフロー
1. プロジェクト内の全タスクをループで回す。
2. タスクが「制約(Constraint)」を持っているか確認する。
3. 制約の種類が「Must Start On(必ず開始)」などの強い制約である場合、その制約日付を取得する。
4. 【核心】 その制約日付と、MS Projectが計算した「実際の開始日」を比較する。
5. 矛盾があれば、エラーログとして記録する。
3. 実装:整合性チェック・エンジン
それでは、実際に動くコードを見ていきましょう。このコードは、単にエラーを出すだけでなく、どのタスクが、どのような理由で矛盾しているのかを明確に報告する設計にしています。
Option Explicit
''' <summary>
''' プロジェクト内の依存関係と制約条件の論理矛盾をチェックするメインエンジン
''' </summary>
Sub RunConsistencyCheckEngine()
Dim t As Task
Dim errorCount As Integer
Dim report As String
Dim constraintDate As Date
Dim calculatedStart As Date
errorCount = 0
report = "--- スケジュール整合性チェック結果 ---" & vbCrLf
' プロジェクト内の全タスクをループ
For Each t In ActiveProject.Tasks
' 空白行(タスクが存在しない行)はスキップ
If Not t Is Nothing Then
If t.Name <> "" Then
' 1. 制約条件が「Must Start On(必ず開始)」または「Must Finish On(必ず終了)」の場合を重点チェック
' これらは依存関係を無視して日付を固定しようとするため、矛盾が起きやすい
If t.ConstraintType = pjConstraintMustStartOn Or _
t.ConstraintType = pjConstraintMustFinishOn Then
' 制約として指定されている日付を取得
constraintDate = t.ConstraintDate
' エンジンが計算した(依存関係を反映した)開始日を取得
calculatedStart = t.Start
' 2. 矛盾の判定ロジック
' 制約日付と計算された開始日が一致しない場合、論理的な矛盾または不自然なラグが発生している
' ※ 誤差を考慮して、微小な時間の違いは許容する設計にするのがプロの技
If Abs(DateDiff("s", constraintDate, calculatedStart)) > 1 Then
errorCount = errorCount + 1
report = report & "[矛盾発生] タスク: " & t.Name & vbCrLf & _
" -> 指定制約日: " & Format(constraintDate, "yyyy/mm/dd") & vbCrLf & _
" -> 計算開始日: " & Format(calculatedStart, "yyyy/mm/dd") & vbCrLf & _
" -> 原因: 制約条件が依存関係と衝突しています。" & vbCrLf & vbCrLf
End If
End If
' 3. 【応用】「Start No Earlier Than(以降に開始)」のチェック
' 前のタスクの終了日よりも、制約日付が「前」になっていないかを確認
If t.ConstraintType = pjConstraintStartNoEarlierThan Then
If t.Predecessors.Count > 0 Then
' 前行タスクの終了日を確認
Dim pred As Task
Dim maxPredFinish As Date
maxPredFinish = 0
For Each pred In t.Predecessors
If pred.Finish > maxPredFinish Then maxPredFinish = pred.Finish
Next pred
' 制約日付が、前のタスクの終了日よりも前にある場合、
' MS Projectは制約を無視して計算するため、整合性が取れていない
If t.ConstraintDate < maxPredFinish Then
errorCount = errorCount + 1
report = report & "[警告: 制約無視] タスク: " & t.Name & vbCrLf & _
" -> 制約(以降に開始): " & Format(t.ConstraintDate, "yyyy/mm/dd") & vbCrLf & _
" -> 前タスク終了日: " & Format(maxPredFinish, "yyyy/mm/dd") & vbCrLf & _
" -> 原因: 制約が前のタスクによって上書きされています。" & vbCrLf & vbCrLf
End If
End If
End If
End If
End If
Next t
' 結果の出力
If errorCount = 0 Then
MsgBox "チェック完了:論理的な矛盾は見つかりませんでした。素晴らしい!", vbInformation, "整合性チェック"
Else
' ログをイミディエイトウィンドウに出力(大量のエラーに対応するため)
Debug.Print report
MsgBox "チェック完了: " & errorCount & " 件の矛盾が見つかりました。" & vbCrLf & _
"詳細はイミディエイトウィンドウを確認してください。", vbExclamation, "整合性チェック"
End If
End Sub
4. コードの解説と「プロの視点」
このコードをただコピペして終わらせてはいけません。なぜこのような書き方をしているのか、その意図を理解してください。
① `If Not t Is Nothing` の重要性
MS Projectの`Tasks`コレクションには、タスクが入力されていない空の行も含まれています。これを考慮せずにプロパティ(`t.Name`など)にアクセスすると、「実行時エラー 91: オブジェクト変数または With ブロック変数が設定されていません」という、初心者が最も陥りやすいエラーでプログラムが止まってしまいます。オブジェクトのライフサイクル(存在するかどうか)を常に意識しましょう。
② `Abs(DateDiff(…)) > 1` のテクニック
日付の比較において、単純に `If constraintDate <> calculatedStart` と書くのは危険です。MS Project内部では、日付は「時刻」の情報も含んでいます。計算の過程で、数秒のズレが生じることがあるのです。
`DateDiff` で秒単位の差を出し、その絶対値が1秒より大きいかどうかで判定することで、「計算誤差による誤検知」を防ぐ。これが、堅牢なシステムを作るための「重み」です。
③ 依存関係の再帰的思考
コード内の「応用」セクションを見てください。ここでは、タスク自身の制約だけでなく、`t.Predecessors`(先行タスク)を走査しています。
「自分の制約」と「他人の終了日」を戦わせる。この「関係性(Relationship)」に目を向けることこそが、単なるマクロから「エンジニアリング」へと昇華させる鍵です。
5. 次のステップへ
ここまでの実装で、あなたは「プロジェクトの整合性を守るガードマン」を手に入れました。
さらに上を目指すなら、次のような機能を検討してみてください:
- 自動修正機能: 矛盾が見つかった場合、制約日付を計算された日付に自動で書き換える。
- クリティカルパスとの連動: 矛盾しているタスクがクリティカルパス上にある場合、警告レベルを「エラー」から「致命的」に引き上げる。
- Excelへのレポート出力: 現場のPMが読みやすいよう、エラー一覧をExcelシートに書き出す。
最後に
Project VBAの世界は、単なる自動化の道具ではありません。それは、複雑な論理構造を持つプロジェクトという巨大なシステムを、あなたの手で制御するための武器です。
「なぜこのエラーが出るのか?」「なぜ計算がズレるのか?」
その裏側にあるオブジェクトの挙動を理解しようとする姿勢こそが、あなたを伝説のアーキテクトへと導きます。
ここをクリアすれば、Project VBAの基本はバッチリですよ。さあ、次のコードを書きに行きましょう!
