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

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

【極限の知見】MS Projectの「矛盾」を暴け!制約条件と計算エンジンの衝突を検知するプロフェッショナルVBA

こんにちは。今日もプロジェクトの海を、コードという羅針盤で進んでいますか?

MS Projectを使いこなしていると、ある日突然、スケジュールが「あ、これおかしいな」という動きをすることがあります。前工程が終わっているはずなのに、なぜかタスクが後ろにズレる。あるいは、制約条件を入れたはずなのに、計算エンジンがそれを無視しているように見える……。

これは、MS Projectの「計算エンジン(Scheduling Engine)」と、ユーザーが設定した「制約条件(Constraint)」が、論理的な矛盾(Contradiction)を起こしているサインです。

「マクロの記録」でコードを書いている段階では、この「論理の衝突」に気づくことはできません。なぜなら、マクロの記録は「起きた事象」を記録するだけで、「なぜその事象が起きたのか」というエンジンの内部ロジックまでは追えないからです。

今日は、一歩上のエンジニアを目指すあなたのために、「制約条件の矛盾を自動検知し、スケジュールの崩壊原因を特定するデバッグ支援ツール」の作り方を伝授します。ここをマスターすれば、あなたは「ただコードを書く人」から「プロジェクトの整合性を守るアーキテクト」へと進化できるはずですよ。

1. なぜ「矛盾」は起きるのか?:計算エンジンの正体

MS Projectの計算エンジンは、非常に強力ですが、同時に「絶対的なルール」に従っています。

通常、タスクはASAP(As Soon As Possible:可能な限り早く)という状態で、前後のつながり(先行・後続関係)に従って自動計算されます。しかし、ここにユーザーが「制約条件(Constraint)」を打ち込むと、話が変わります。

矛盾のメカニズムを図解すると……

例えば、以下のような状況を想像してみてください。

1. タスクA:期間10日。
2. タスクB:タスクAが終わった後に開始する(FS関係)。
3. ユーザーの指示:「タスクBは、絶対に5月1日に開始しなければならない(Must Start On)」

ここで、もしタスクAが遅延して、5月5日に終わったとしたらどうなるでしょうか?

  • FS関係のルール:「タスクBは5月6日に開始すべき」
  • 制約条件のルール:「タスクBは5月1日に開始すべき」

この瞬間、論理的な「矛盾」が発生します。 MS Projectのエンジンは、どちらかのルールを優先(あるいは妥協)して計算を行いますが、これが原因で「見た目上のスケジュール」と「論理的なつながり」が乖離し、プロジェクト全体の整合性が崩壊するのです。

2. 検証アルゴリズムの設計図

プロフェッショナルなツールを作るには、「何をもって矛盾とするか」という定義が必要です。今回は、以下のステップで検証を行うアルゴリズムを構築します。

1. 対象の抽出: 全タスクをループで回す。
2. 制約の判定: そのタスクに「Must Start On(Must)」や「Start No Earlier Than(SNET)」などの、計算を強制する制約が入っているかを確認する。
3. 日付の照合: 「設定された制約日付(`ConstraintDate`)」と、「エンジンが実際に算出した開始日(`Start`)」を比較する。
4. 乖離の検知: 両者の日付に差(ギャップ)がある場合、それを「矛盾の疑い」として報告する。

3. 実装:矛盾検知エンジン(VBAコード)

それでは、実際に使えるコードを見ていきましょう。このコードは、単にエラーを出すだけでなく、どのタスクが、どのような矛盾を起こしているかを具体的に特定します。

Option Explicit

''' <summary>
''' プロジェクト内のタスクにおける制約条件と計算結果の矛盾を検知する
''' </summary>
Sub DetectConstraintContradictions()
    Dim t As Task
    Dim mismatchCount As Integer
    Dim report As String
    Dim toleranceMinutes As Long
    
    ' 判定の許容範囲(分単位)。計算誤差を考慮して5分程度に設定
    toleranceMinutes = 5
    mismatchCount = 0
    report = "【制約条件矛盾レポート】" & vbCrLf & String(30, "-") & vbCrLf
    
    ' 高速化のため、画面更新を停止
    Application.ScreenUpdating = False
    
    ' 全タスクを走査
    For Each t In ActiveProject.Tasks
        ' 空のタスク(削除済みなど)はスキップ
        If Not t Is Nothing Then
            
            ' 1. 制約条件の種類を確認
            ' csMustStartOn, csStartNoEarlierThan などの「強い制約」を対象にする
            If t.ConstraintType <> csAsap And t.ConstraintType <> csAsSoonAsPossible Then
                
                ' 2. 制約日付と実際の開始日を比較
                ' ConstraintDate(ユーザーが指定した日付)と Start(エンジンが計算した日付)
                ' DateDiff関数を使用して、両者の差分を分単位で取得
                If Abs(DateDiff("n", t.ConstraintDate, t.Start)) > toleranceMinutes Then
                    
                    ' 矛盾を発見した場合、レポートに記録
                    mismatchCount = mismatchCount + 1
                    report = report & vbCrLf & _
                             "ID: " & t.ID & " | タスク名: " & t.Name & vbCrLf & _
                             "   → 指定日付: " & t.ConstraintDate & vbCrLf & _
                             "   → 計算結果: " & t.Start & vbCrLf & _
                             "   → 制約タイプ: " & GetConstraintName(t.ConstraintType) & vbCrLf
                End If
                
            End If
        End If
    Next t
    
    Application.ScreenUpdating = True
    
    ' 結果の出力
    If mismatchCount > 0 Then
        report = report & vbCrLf & String(30, "-") & vbCrLf & _
                 "合計 " & mismatchCount & " 件の矛盾が検出されました。"
        MsgBox report, vbExclamation, "検証完了(矛盾あり)"
        ' 必要に応じて、詳細をイミディエイトウィンドウにも出力
        Debug.Print report
    Else
        MsgBox "矛盾は見つかりませんでした。スケジュールは論理的に整合しています。", vbInformation, "検証完了"
    End If
End Sub

''' <summary>
''' 制約タイプの定数を分かりやすい文字列に変換する補助関数
''' </summary>
Private Function GetConstraintName(ByVal cType As PjConstraintType) As String
    Select Case cType
        Case csMustStartOn: GetConstraintName = "Must Start On"
        Case csMustFinishOn: GetConstraintName = "Must Finish On"
        Case csStartNoEarlierThan: GetConstraintName = "Start No Earlier Than"
        Case csStartNoLaterThan: GetConstraintName = "Start No Later Than"
        Case csFinishNoEarlierThan: GetConstraintName = "Finish No Earlier Than"
        Case csFinishNoLaterThan: GetConstraintName = "Finish No Later Than"
        Case Else: GetConstraintName = "Other (" & cType & ")"
    End Select
End Function

4. プロの視点:このコードの「肝」はどこか?

このコードをただコピペして終わりにするのはもったいないです。プロとして意識すべきポイントが3つあります。

① `DateDiff` による「許容範囲(Tolerance)」の設定

コンピュータの世界では、日付や時間の計算において、ごくわずかな「浮動小数点誤差」が生じることがあります。`t.ConstraintDate = t.Start` と直接比較してしまうと、人間には同じに見えても、0.000001秒の差で「不一致」と判定されてしまいます。
そこで、`DateDiff(“n”, …)` を使い、「数分以内の差なら同一とみなす」という、実務的なアプローチを取っています。

② `ConstraintType` の選別

すべての制約をチェックする必要はありません。`csAsap`(可能な限り早く)は、実質的に「制約なし」と同じ状態です。これを除外することで、計算エンジンの本来の動き(自動計算)と、ユーザーによる強制介入(制約)の境界線を明確にしています。

③ ユーザー体験(UX)への配慮

エラーを見つけるだけでなく、「何がどう違うのか」を提示すること。`GetConstraintName` のような補助関数を使って、エンジニアではないプロジェクトマネージャー(PM)が見ても、「あ、このタスクの開始日が強制されてるから、計算がズレてるんだな」と直感的に理解できる情報を提供することが、優れたツールの条件です。

5. まとめ:次なるステップへ

お疲れ様でした!これであなたは、MS Projectのブラックボックス化しがちな「計算の矛盾」を、VBAの力で可視化する術を手にしました。

「マクロの記録」は、あくまで「操作の自動化」です。
しかし、今回のような「ロジックの検証」こそが、「業務の自動化・高度化」の本質です。

ここをクリアすれば、Project VBAの基本はバッチリですよ。

次は、この検知した矛盾タスクを自動的に赤色に塗ったり、あるいは矛盾を解消するように制約を自動解除する、といった「自己修復機能」の実装に挑戦してみるのも面白いかもしれませんね。

それでは、また次のコードでお会いしましょう!

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