「ASAP」と「開始日固定」を極める:計算エンジンを制御するプロフェッショナルな設計思想
プロジェクト管理の現場において、スケジュールの「柔軟性」と「拘束力」のコントロールは、PM(プロジェクトマネージャー)にとって生命線だ。
タスクを「可能な限り早く(ASAP: As Soon As Possible)」動かせる状態にしておくのか、それとも「この日から動かせない(Fixed Start)」と釘を刺すのか。この切り替えを、いちいちプロパティパネルから手作業で行っているようでは、自動化エンジニアを名乗る資格はない。
しかし、単に `ConstraintType` を書き換えるだけのスクリプトを書くのは、素人の仕事だ。MS Projectの強力すぎる「再計算エンジン」を理解せずにループ処理を走らせれば、数千タスクあるプロジェクトでは、アプリケーションがフリーズし、ユーザーの信頼を失うことになる。
本稿では、「ASAP」と「開始日固定」をトグル(交互切り替え)するUIツールを題材に、パフォーマンス、堅牢性、そして設計思想に基づいたプロダクションレベルのコード実装を伝授する。
1. 陥りやすい罠:なぜ「単純なループ」ではいけないのか
初心者が書くコードは、おそらくこうなるだろう。
' ❌ 避けるべきアンチパターン
Sub BadToggle()
Dim t As Task
For Each t In ActiveSelection.Tasks
If t.ConstraintType = pjConstraintAsSoonAsPossible Then
t.ConstraintType = pjConstraintStartNoEarlierThan
Else
t.ConstraintType = pjConstraintAsSoonAsPossible
End If
Next t
End Sub
このコードには、致命的な欠陥が2つある。
第一の欠陥:再計算エンジンの暴走
MS Projectは、タスクの制約条件が一つ変わるたびに、プロジェクト全体のスケジュールを再計算しようとする。もし選択されたタスクが100個あれば、100回のフルスキャンと計算が走る。これは計算量の爆発を意味する。
第二の欠陥:オブジェクト・ライフサイクルの不安定性
`ActiveSelection.Tasks` を直接ループ内で操作しながら、制約条件(これにより日付が変わる)を変更すると、選択範囲のインデックスや内部的なオブジェクトの状態が変化し、予期せぬエラーやスキップが発生する可能性がある。
2. アーキテクチャ設計:堅牢なツールの3原則
プロフェッショナルなツールを設計する場合、以下の3点を実装に組み込む。
1. 計算制御(Calculation Control): 処理開始前に計算を「手動」に切り替え、終了後に「自動」に戻す。これにより、計算コストを $O(N)$ から $O(1)$ に近似させる。
2. 状態の分離(State Isolation): 変更対象となるタスクを、一度「配列」や「コレクション」に抽出してから処理を行う。これにより、変更による副作用からループ構造を守る。
3. エラーハンドリングと復旧(Error Recovery): 万が一エラーが発生しても、必ず計算モードを「自動」に戻し、ユーザーの作業環境を破壊しない。
3. プロダクション・コード:トグル・エンジン
以下に、実務でそのまま利用可能な、極めて堅牢な実装例を示す。
Option Explicit
' ==============================================================================
' Module: SchedulingEngineController
' Description: タスクの制約条件をASAPと開始日固定の間でトグルする
' ==============================================================================
''' <summary>
''' 選択されたタスクの制約条件をトグルするメインルーチン
''' </summary>
Public Sub ToggleTaskConstraints()
Dim targetTasks As Collection
Dim t As Task
Dim originalCalculation As Long
' 1. 事前チェック:選択されているか?
If ActiveSelection.Tasks.Count = 0 Then
MsgBox "タスクが選択されていません。", vbExclamation, "警告"
Exit Sub
End If
' 2. 状態の保存と最適化
' 計算モードを一時的に手動に設定し、再計算のオーバーヘッドを最小化する
originalCalculation = Application.Calculation
Application.Calculation = pjCalculationManual
' 3. 変更対象の隔離 (State Isolation)
' 変更中にSelectionの状態が変わっても安全なよう、一度コレクションに格納する
Set targetTasks = New Collection
Dim selTask As Task
For Each selTask In ActiveSelection.Tasks
targetTasks.Add selTask
Next selTask
On Error GoTo ErrorHandler
' 4. 実行フェーズ
' トグルロジックの核心
For Each t In targetTasks
If t.ConstraintType = pjConstraintAsSoonAsPossible Then
' ASAP -> 開始日固定 (Start No Earlier Than) へ
' 現在の開始日を維持したまま、制約を強める
t.ConstraintType = pjConstraintStartNoEarlierThan
Else
' 固定 -> ASAP へ
t.ConstraintType = pjConstraintAsSoonAsPossible
End If
Next t
' 5. 完了処理
' 最後に一度だけ再計算を実行する
Application.Calculate
MsgBox targetTasks.Count & " 件のタスクの制約条件を切り替えました。", vbInformation, "完了"
CleanExit:
' 6. 環境の復元 (重要: エラー時も必ず通る)
Application.Calculation = originalCalculation
Exit Sub
ErrorHandler:
MsgBox "エラーが発生しました: " & Err.Description, vbCritical, "致命的なエラー"
Resume CleanExit
End Sub
コードの解説:なぜこれが「強い」のか
- `Application.Calculation = pjCalculationManual`: これこそがパフォーマンスの鍵だ。ループ内での再計算を封じ込め、処理速度を劇的に向上させている。
- `targetTasks` コレクションへの格納: `ActiveSelection` を直接回すのではなく、一旦メモリ上の `Collection` にコピーしている。これにより、`t.ConstraintType` の変更に伴うプロジェクトの再構成(Re-indexing)による副作用を完全に遮断している。
- `originalCalculation` の保持: ユーザーが元々「手動計算」に設定していた場合、ツール終了後に勝手に「自動計算」に変えてしまうのは、設計として不作法だ。元の状態を記憶し、正確に復元するのがプロの仕事だ。
4. UI設計:ツールバーへの統合
ロジックができたら、それをユーザーが触れる形にする必要がある。MS Projectでは、`CommandBars`(レガシーだが実装が容易)または `Ribbon XML` を使用する。
実務的なスピード感を重視するなら、クイックアクセスツールバー(QAT)に登録させるための「マクロの公開」を行うのが最も効率的だ。
実務的なUI展開のアドバイス
1. アイコンの設計: 「トグル」を意味するアイコン(例えば、矢印が循環しているもの、あるいは「カレンダー」と「時計」が混ざったもの)を検討せよ。
2. ショートカットキーの割り当て: 頻繁に使うツールであれば、`Alt + T` などのショートカットを割り当てられるよう、アドイン化することを推奨する。
5. まとめ:エンジニアとしての規律
「動けばいい」という思考は、スケールするシステムでは毒となる。
今回紹介した実装は、単に制約条件を書き換えるだけではない。「アプリケーションの計算リソースをどう管理するか」「オブジェクトの不整合をどう防ぐか」「ユーザーの作業環境をどう守るか」という、エンジニアリングの本質的な問いに対する回答だ。
大規模なプロジェクトファイルを扱う現場では、君が書く一行のコードが、数分間のフリーズを生むか、あるいは一瞬の魔法となるかを分ける。常に、オブジェクトのライフサイクルとエンジンの挙動を意識した、格調高いコードを記述せよ。
