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の基本はバッチリですよ。
次は、この矛盾を見つけた後に「自動的に制約を解除する」あるいは「制約に合わせて先行タスクをずらす」といった、自動修正エンジンへの挑戦をお待ちしています。
挑戦を続けてください。応援していますよ!
