【入門編】プロ向け:依存関係と制約条件の矛盾をVBAで検知する「整合性チェック」エンジン – Project VBA解析バイブル

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

Project VBAの深淵へようこそ:スケジュール計算エンジンを掌握する「整合性チェック」エンジンの構築

「マクロの記録」を使って、なんとか自動化の第一歩を踏み出したあなた。素晴らしい一歩です。しかし、プロジェクト管理のプロフェッショナルとして、MS Projectという「巨大な計算エンジン」を truly(真に)制御するためには、単なる操作の自動化を超えた「ロジックの自動化」が必要になります。

今日、私たちが挑むのは、多くのPM(プロジェクトマネージャー)が頭を抱える問題です。
それは、「タスク間の依存関係(先行・後行)」と「個別の制約条件(Must Start Onなど)」が衝突し、スケジュールが論理的に破綻している箇所をどう見つけ出すか、という課題です。

「マクロで日付を書き換える」のは簡単です。しかし、「なぜその日付が不適切なのか」を判定するエンジンを作るには、MS Projectの思考回路(スケジューリング・エンジン)を理解しなければなりません。

さあ、準備はいいですか? コードの裏側に潜む「オブジェクトの命」を感じながら、極限の整合性チェックエンジンを構築していきましょう。

1. なぜ「矛盾」は生まれるのか?:スケジューリング・エンジンの葛藤

MS Projectは、非常に強力な計算エンジンを持っています。しかし、このエンジンは常に「二つの命令」の間で板挟みになります。

1. 論理的命令(Predecessors): 「タスクAが終わらない限り、タスクBは始められない」という物理的なつながり。
2. 強制的命令(Constraints): 「タスクBは、何が何でも○月○日に開始しなければならない」という人間が決めたルール。

例えば、タスクA(先行)が5月10日に終わる予定なのに、タスクB(後行)に「5月5日に開始せよ(Must Start On)」という制約を課したとしましょう。
この瞬間、スケジュールには「論理的な矛盾」が発生します。MS Projectは無理やり計算を成立させようとしますが、それは現実とは乖離した、いわば「幽霊のようなスケジュール」になってしまうのです。

これを手作業で探すのは至難の業です。だからこそ、私たちは「検知エンジン」を作るのです。

2. 実装:整合性チェック・エンジンの設計図

プロフェッショナルなツールを作る際、私たちは「何を見るべきか」を明確にします。今回のエンジンがチェックするのは以下のパターンです。

  • パターンA: 先行タスクの終了日が、自身の「開始制約日」よりも後ろになっている。
  • パターンB: 先行タスクの終了日が、自身の「終了制約日」よりも後ろになっている。

これらをVBAで、オブジェクトのプロパティを舐めるように精査していきます。

整合性チェック・エンジン(VBAコード)

以下のコードを標準モジュールに貼り付けてください。初心者の方でも、コメントを丁寧に読めば「何が起きているか」が手に取るようにわかるはずです。

Option Explicit

''' <summary>
''' スケジュールの整合性をチェックし、依存関係と制約条件の矛盾を報告するエンジン
''' </summary>
Sub IntegrityCheckEngine()
    Dim t As Task
    Dim pred As Task
    Dim conflictCount As Integer
    Dim reportMsg As String
    
    conflictCount = 0
    reportMsg = "【整合性チェック結果】" & vbCrLf
    
    ' プロジェクト内の全タスクをループ処理
    ' Taskオブジェクトのライフサイクルを意識し、Nothingでないことを確認するのが鉄則
    For Each t In ActiveProject.Tasks
        If Not t Is Nothing Then
            
            ' 1. 制約条件(ConstraintType)が設定されているか確認
            ' pjConstraintNone (0) 以外の場合、何らかの制約がある
            If t.ConstraintType <> pjConstraintNone Then
                
                ' 2. そのタスクに「先行タスク(Predecessors)」があるか確認
                ' 先行関係がない制約は、単なる「固定日付」なので矛盾判定からは除外する
                If t.PredecessorLinks.Count > 0 Then
                    
                    ' 各先行タスクとの関係を精査
                    Dim link As PredecessorLink
                    For Each link In t.PredecessorLinks
                        Set pred = link.FromPredecessor ' 先行タスクをオブジェクトとして取得
                        
                        If Not pred Is Nothing Then
                            
                            ' --- 矛盾検知ロジックの核心 ---
                            
                            ' ケース1: 「開始制約」との矛盾
                            ' 制約日が、先行タスクの終了日よりも前(過去)に設定されていないか?
                            If (t.ConstraintType = pjTaskMustStartOn Or t.ConstraintType = pjTaskStartNoEarlierThan) Then
                                If pred.Finish > t.ConstraintDate Then
                                    conflictCount = conflictCount + 1
                                    reportMsg = reportMsg & "⚠️ 矛盾検出: タスク[" & t.Name & "] " & _
                                                "先行[" & pred.Name & "]の終了日(" & pred.Finish & ")が、" & _
                                                "制約日(" & t.ConstraintDate & ")を過ぎています。" & vbCrLf
                                End If
                            End If
                            
                            ' ケース2: 「終了制約」との矛盾
                            ' 制約日が、先行タスクの終了日よりも前(過去)に設定されていないか?
                            If t.ConstraintType = pjTaskMustFinishOn Then
                                If pred.Finish > t.ConstraintDate Then
                                    conflictCount = conflictCount + 1
                                    reportMsg = reportMsg & "⚠️ 矛盾検出: タスク[" & t.Name & "] " & _
                                                "先行[" & pred.Name & "]の終了日(" & pred.Finish & ")が、" & _
                                                "制約終了日(" & t.ConstraintDate & ")を過ぎています。" & vbCrLf
                                End If
                            End If
                            
                        End If
                    Next link
                End If
            End If
        End If
    Next t

    ' 結果の出力
    If conflictCount = 0 Then
        MsgBox "チェック完了:矛盾は見つかりませんでした。素晴らしいスケジュールです!", vbInformation, "整合性チェック"
    Else
        MsgBox reportMsg, vbExclamation, "整合性チェック完了 (" & conflictCount & "件の矛盾)"
    End If
End Sub

3. コードの解剖:プロの視点

このコードをただコピペするだけでは、あなたはまだ「マクロ利用者」です。「エンジニア」になるために、重要なパーツを紐解いてみましょう。

① `If Not t Is Nothing Then` の重要性

MS Projectの `Tasks` コレクションには、タスクが削除された跡地である「空のセル」が含まれることがあります。これを無視してプロパティにアクセスすると、「実行時エラー 91: オブジェクト変数または With ブロック変数が設定されていません」という、VBA初心者が最も恐れるエラーに遭遇します。オブジェクトの存在確認は、プロの作法です。

② `t.ConstraintType` と `t.ConstraintDate`

ここがこのエンジンの心臓部です。

  • `ConstraintType`: そのタスクに「強制力」があるかどうかを判定します。
  • `ConstraintDate`: 制約が課されている「具体的な日付」を保持しています。

単に `t.Start`(タスクの開始日)を見るのではなく、`ConstraintDate` を見ることで、「ユーザーが本来意図した制約日」と「計算結果としての開始日」の乖離を判定できるのです。

③ `PredecessorLinks` コレクションへのアクセス

タスクには `Predecessors`(先行タスクのIDリスト)というプロパティもありますが、それだと「どのタスクがどう繋がっているか」という詳細が分かりにくい。`PredecessorLinks` を使うことで、リンクそのものをオブジェクトとして扱い、洗練されたコードが書けます。

4. 陥りやすいエラーと、パフォーマンスの重み

このレベルのツールを運用する際、あなたは必ず次の壁にぶつかります。

パフォーマンスの低下(計算の重み)

プロジェクトのタスク数が数千、数万に及ぶ場合、このループ処理は重くなります。なぜなら、`pred.Finish` を参照するたびに、MS Projectの背後にある計算エンジンが「最新の状態か?」を確認しようとするからです。
プロの知恵: 大規模プロジェクトでは、最初に `Application.Calculation = pjCalculationManual` で計算を止め、全てのチェックが終わった後に `pjCalculationAutomatic` に戻すことで、劇的に速度を改善できます。

日付の比較における「時刻」の罠

MS Projectの内部では、日付は「シリアル値」として管理されており、時刻情報を含んでいます。
`pred.Finish > t.ConstraintDate` という比較において、もし終了日が「10:00」で制約日が「10:00」だった場合、極めて微細な差で「矛盾」と判定されることがあります。実務では `DateValue()` 関数を使って、時刻を切り捨てて比較する工夫が必要になる場面もあります。

5. おわりに:ここからがあなたの本当のスタートです

いかがでしたか?
単に「日付を書き換えるマクロ」を作るのと、「スケジュールの論理的矛盾を検知するエンジン」を作るのとでは、コードの深みが全く違いますよね。

今回学んだ「オブジェクトの存在確認」「制約と論理の分離」「計算エンジンの特性への配慮」。これらを意識するだけで、あなたの書くコードは「動くもの」から「信頼されるシステム」へと進化します。

ここをクリアすれば、Project VBAの基本はバッチリですよ。
次は、この矛盾を見つけた後に「自動的に制約を解除する」あるいは「制約に合わせて先行タスクをずらす」といった、自動修正エンジンへの挑戦をお待ちしています。

挑戦を続けてください。応援していますよ!

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