【Project VBA 掌握】テンプレート汚染を排除せよ:スケジュール計算エンジンを正常化する「制約条件クリーンアップ」の極意
プロジェクト管理の現場で、よくある「悲劇」を知っているだろうか?
過去の成功プロジェクトからタスクリストをコピーし、新しいプロジェクトのテンプレートとして流用する。一見、効率的な手法に見える。しかし、その中身を精査したとき、多くのプロジェクトマネージャーは絶望することになる。
「なぜだ? 前提条件(Predecessors)は正しく設定されているはずなのに、タスクがスケジュール通りに動かない。日付が固定されていて、クリティカルパスが全く計算されない……」
その原因は、見えない場所に潜んでいる。「制約条件(Constraints)」の残存だ。
今回は、MS Projectの心臓部である「スケジュール計算エンジン」を正常に機能させるために不可欠な、制約条件の自動クリーンアップについて、設計思想からプロダクションレベルのコード実装までを徹底的に解説する。
1. なぜ「制約条件」がプロジェクトを破壊するのか
MS Projectの本質は、単なる「日付のリスト」ではない。タスク間の依存関係に基づき、日付を動的に再計算する「動的スケジュール計算エンジン」である。
このエンジンが自由自在に動くためには、タスクが「ASAP(できるだけ早く)」の状態、つまり `pjTaskConstraintNone` である必要がある。
しかし、過去のプロジェクトからタスクをコピーしたり、特定のタスクを「Must Start On(○月○日に開始しなければならない)」として設定していた場合、その制約(Constraint)という名の枷(かせ)も一緒にコピーされてしまう。
制約条件の「毒性」
制約条件(特に `Must Start On` や `Must Finish On`)が残っていると、以下の事態を招く。
1. 連鎖反応の停止: 前行のタスクが遅延しても、制約のあるタスクは動かない。結果、スケジュール全体が嘘をつき始める。
2. スラック(余裕)の消失: 計算エンジンが制約を優先するため、本来存在するはずのフロート(余裕)が不自然にゼロ、あるいは負の値として表示される。
3. リソース平準化の失敗: 制約に縛られたタスクは、リソースの空き状況に関わらずその日付に居座り続けるため、リソースレベル化が機能しない。
テンプレート適用時、我々エンジニアが成すべきは、タスクの内容をコピーすることではなく、「計算エンジンが自由に動けるクリーンな器」を再構築することなのだ。
2. 設計思想:堅牢なクリーンアップ・アルゴリズム
単に「全ての制約を消す」というコードを書けばいいわけではない。実務レベルのツールとして、以下の3点を設計に組み込む必要がある。
① オブジェクトの存在チェック(Null参照の回避)
MS Projectの `Tasks` コレクションには、削除されたタスクや空のインデックスが含まれることがある。`If Not t Is Nothing` のチェックを怠れば、実行時に即座にランタイムエラーでクラッシュする。
② 自動スケジュールの強制(Auto Scheduledへの変換)
制約を消しても、タスクが「手動スケジューリング(Manually Scheduled)」のままであれば、計算エンジンは動かない。制約の削除と同時に、タスクを「自動スケジューリング」へと強制的にシフトさせる必要がある。
③ 計算負荷の最適化
数千タスク規模のプロジェクトにおいて、一つ一つの制約を書き換えるたびに再計算(Recalculation)が走ると、処理時間は指数関数的に増大する。計算モードを一時的に「手動」に切り替え、一括処理後に再計算させるのがプロの作法だ。
3. プロダクションコード:Constraint Purifier
以下に、そのまま実務の標準モジュールとして組み込める、堅牢なVBAコードを提示する。
Option Explicit
''' <summary>
''' プロジェクト内の全タスクから不要な制約条件を排除し、
''' スケジュール計算エンジンを正常化する。
''' </summary>
Public Sub PurifyProjectConstraints()
Dim t As Task
Dim cleanCount As Long
Dim manualCount As Long
Dim errorCount As Long
' 1. パフォーマンス最適化: 計算モードを一時的に停止
' これにより、ループ中の不必要な再計算を防ぎ、実行速度を劇的に向上させる
Application.Calculation = pjCalculationManual
On Error GoTo ErrorHandler
cleanCount = 0
manualCount = 0
errorCount = 0
Debug.Print "--- Constraint Cleanup Started ---"
' 2. Tasksコレクションの走査
' Tasksコレクションは、空の要素を含む可能性があるため、Nothingチェックが必須
For Each t In ActiveProject.Tasks
If Not t Is Nothing Then
' --- 制約条件のクリーンアップ ---
' pjTaskConstraintNone (ASAP) 以外の制約がある場合、リセットする
If t.ConstraintType <> pjTaskConstraintNone Then
t.ConstraintType = pjTaskConstraintNone
cleanCount = cleanCount + 1
End If
' --- スケジュールモードの強制 ---
' 制約を消しても「手動」ではエンジンは動かない。
' すべてのタスクを「自動スケジューリング」へ移行させる。
If t.Manual = True Then
t.Manual = False
manualCount = manualCount + 1
End If
End If
Next t
' 3. 後処理: 計算モードを自動に戻し、一括再計算を実行
Application.Calculation = pjCalculationAutomatic
ActiveProject.Calculate
' 結果のレポート
MsgBox "クリーンアップ完了", vbInformation, "Success"
Debug.Print "Cleaned Constraints: " & cleanCount
Debug.Print "Converted to Auto: " & manualCount
Debug.Print "--- Cleanup Finished ---"
Exit Sub
ErrorHandler:
' エラー発生時は計算モードを必ず自動に戻す(戻さないとMS Projectが固まったようになる)
Application.Calculation = pjCalculationAutomatic
MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical, "Error"
errorCount = errorCount + 1
Debug.Print "Error encountered: " & Err.Description
End Sub
4. アーキテクトの視点:さらなる高度化に向けて
このスクリプトは「テンプレート適用時」の第一段階として完璧だが、実務ではさらに踏み込んだ要件が出てくるだろう。
データベース連携時の注意点
もし、このスクリプトを外部のSQL ServerやExcelデータベースからタスクをインポートするワークフローの一部として組み込むなら、「インポート直後」にこのクリーンアップを叩くように設計せよ。データソース側で日付が固定されている場合、インポートした瞬間に「制約付きタスク」としてMS Projectに認識されるからだ。
「意図的な制約」をどう扱うか
全ての制約を消すと、プロジェクトマネージャーが意図的に設定した「このタスクだけは絶対に○日以降に開始させたい(Start No Earlier Than)」といった制約まで破壊してしまう。
これを防ぐには、「特定のフラグ(Flag1〜Flag28)が立っているタスクはスキップする」というロジックを追加すべきだ。
- `If t.Flag1 = False Then t.ConstraintType = pjTaskConstraintNone`
といった形で、業務ルールに基づいた「例外処理」を設計に組み込むのが、真に使いやすいツールの条件である。
結論
テンプレートのコピペは「データのコピー」ではなく、「ロジックの継承」である。
しかし、そのロジックに古い制約という名のノイズが混じっていれば、プロジェクトの未来は予測不能なものとなる。
VBAを書くときは、単に「動くコード」を書くのではない。「対象となるオブジェクト(この場合はスケジュール計算エンジン)が、最も健全な状態で動作するための環境を整えるコード」を書け。
それが、エンジニアリングの本質だ。
