制約の深淵を制御せよ:MS Project計算エンジンを完全掌握する「強制日付書き込み」の設計思想
MS Projectを単なる「表計算ソフトの延長」だと思っているエンジニアは、必ず一度は、計算エンジンが吐き出す不可解な挙動、あるいは「書き換えたはずの日付が、実行した瞬間に元の値に戻る」という怪奇現象に直面する。
これはバグではない。MS Projectの「制約(Constraint)」と「計算エンジン(Calculation Engine)」のライフサイクルを理解していないことによる、設計上の敗北である。
本記事では、プロフェッショナルな自動化ツールを開発するにあたり、避けては通れない「制約を無視した日付の強制書き込み」について、そのメカニズムの解体と、計算エンジンを壊さずに制御するための堅牢なアーキテクチャを伝授する。
1. なぜ `Task.Start = Date` は「素人」の書き方なのか
多くの開発者が、以下のようなコードを書いてプロジェクトを崩壊させる。
' ⚠️ 危険なコード:設計思想が欠如している
For Each t In ActiveProject.Tasks
If t.ID = 10 Then t.Start = Date ' 直接書き込み
Next t
なぜこれが危険なのか。MS Projectのタスクには、「制約タイプ(ConstraintType)」と「制約日付(ConstraintDate)」という、プロパティの「根源」となるデータが存在するからだ。
計算エンジンの論理構造
MS Projectの計算エンジンは、以下の優先順位で日付を決定する。
1. 制約条件(Must Start On, Must Finish On 等)
2. 依存関係(Predecessors/Successors)による計算結果
3. ASAP/ALAP(制約なし)によるスケジュール
もし、あるタスクに `pjTaskConstraintMustStartOn`(開始日固定)が設定されている場合、君が `Task.Start` にいくら新しい日付を代入しようとも、計算エンジンが走った瞬間に、エンジンは「制約に従い、正しい(はずの)日付」へとタスクを押し戻してしまう。
直接 `Start` プロパティを叩く行為は、「エンジンの論理」と「データの整合性」の間に矛盾を発生させる行為であり、不整合が蓄積されると、プロジェクト全体の再計算が無限ループに陥るか、あるいは全く予測不能なスケジュールへと変貌する。
2. アーキテクチャの設計:制約の「剥離」と「再構築」
堅牢なツールを作るなら、日付を書き換える前に、そのタスクを「制約の支配下」から一時的に解放し、書き込み後に「適切な制約」を再定義するプロセスが必要だ。
エンジニアリングの観点からは、以下の3フェーズで処理を設計すべきである。
1. Deconstruction(解体): 対象タスクの現在の制約タイプを退避し、制約を `pjTaskConstraintAsSoonAsPossible`(制約なし)へ変更する。
2. Injection(注入): 目的の日付を `Start` または `Finish` に書き込む。
3. Re-stabilization(再安定化): 必要に応じて制約を再設定し、計算エンジンを強制的に同期させる。
3. プロダクション・レディな実装例
以下に、単なる関数ではなく、計算エンジンの負荷を最小限に抑え、かつ整合性を担保するためのクラス設計に近い実装パターンを示す。
' @module: TaskController
' @description: MS Projectの計算エンジンを制御し、安全に日付を強制変更する
Option Explicit
''' <summary>
''' タスクの制約を安全に無視して日付を強制的に設定する
''' </summary>
''' <param name="t">対象となるTaskオブジェクト</param>
''' <param name="targetDate">設定したい日付</param>
''' <param name="isHardConstraint">Trueの場合、Must Start On制約を付与する</param>
Public Sub ForceTaskDate(ByRef t As MSProject.Task, ByVal targetDate As Date, Optional ByVal isHardConstraint As Boolean = False)
' 1. エラーハンドリングとガード節
If t Is Nothing Then Exit Sub
If t.Type = pjTaskMilestone Then
Err.Raise vbObjectError + 513, "TaskController", "マイルストーンの日付操作は制約設計が異なるため禁止されています。"
End If
On Error GoTo ErrorHandler
' 2. パフォーマンス最適化:計算モードを一時的に「手動」にする
' 大量更新を行う場合は、この制御を呼び出し元で行うべきである
Dim originalCalcMode As MSProject.Calculation
originalCalcMode = Application.Calculation
Application.Calculation = pjManual
' 3. 制約の剥離 (Deconstruction)
' 現在の制約を保存する必要がある場合はここで変数に格納する
Dim originalConstraint As MSProject.TaskConstraintType
originalConstraint = t.ConstraintType
' 制約を「制約なし(ASAP)」にリセットして、エンジンの抵抗を無効化する
t.ConstraintType = pjTaskConstraintAsSoonAsPossible
' 4. 日付の注入 (Injection)
' Startを書き換えることで、エンジンは論理的な矛盾なく日付を受け入れる
t.Start = targetDate
' 5. 制約の再構築 (Re-stabilization)
If isHardConstraint Then
' 強制的な固定日付として設定する場合
t.ConstraintType = pjTaskConstraintMustStartOn
Else
' 元の制約が「制約なし」以外だった場合、必要に応じて復元するロジックをここに挟む
' 今回はシンプルにするため、制約なしとして確定させる
t.ConstraintType = pjTaskConstraintAsSoonAsPossible
End If
' 6. 計算エンジンの同期
' 書き込み直後に計算を実行し、依存関係への波及を確定させる
Application.Calculate
CleanUp:
' 7. 環境の復元
Application.Calculation = originalCalcMode
Exit Sub
ErrorHandler:
MsgBox "Task Force Update Error: " & Err.Description, vbCritical
Resume CleanUp
End Sub
このコードの「プロの視点」
- `Application.Calculation = pjManual`: これを忘れると、1タスク書き換えるごとにプロジェクト全体の再計算が走り、数千タスクあるプロジェクトでは処理が致命的に遅くなる。
- `ConstraintType` の操作: `Start` を変える前に `ConstraintType` を `pjTaskConstraintAsSoonAsPossible` に落とす。これが「エンジンの抵抗」を回避する鍵だ。
- エラーハンドリングと復元: 途中でエラーが起きても、必ず `Application.Calculation` を元の状態に戻す。これを怠ると、ユーザーが手動で操作しようとした時に「計算が走らない!」というクレームに繋がる。
4. データベース・外部連携における「時間的乖離」の注意点
この技術を、ExcelやSQL Serverなどの外部データベースと連携させる場合、さらに高度な設計が求められる。
データの「真実」はどこにあるか?
DBから取得した日付をMS Projectに書き込む際、エンジンの再計算によって「DBの値」と「Project上の値」がズレることがある(例:前タスクとの関係で、書き込んだ日付が後ろに押し出されるケース)。
設計指針:
1. 同期の冪等性(Idempotency)を確保せよ:
同じ日付を何度書き込んでも、結果が同じになるように設計すること。制約を制御せずに書き込むと、実行のたびに日付が微増減する「ドリフト現象」が起きる。
2. 「決定論的」な更新プロセス:
DBから値を流し込む際は、必ず「制約の解除 → 書き込み → 再計算 → 結果の検証」というサイクルをユニットテストレベルで確立しておくこと。
3. 書き戻し(Write-back)の設計:
Project側で計算された「最終的な日付」をDBに戻す必要がある場合、`Task.Start` をそのまま戻すのではなく、`ConstraintType` も含めた「状態」をセットで管理・同期する仕組みを構築せよ。
結論
MS ProjectのVBA開発において、`Task.Start` プロパティは単なるデータ保持用ではない。それは、複雑な制約グラフにおける一つのノードの座標である。
座標を書き換えるならば、そのノードを縛っている「制約」という鎖を一時的に解き、座標を書き換えた後、再び適切な鎖で繋ぎ直す。この「解体と再構築」のプロセスこそが、バグのない、極めて堅牢な自動化ツールを生む唯一の道である。
プロフェッショナルとして、エンジンの挙動を「予測」するのではなく、「制御」せよ。
