Project VBAにおける制約条件の極致:マイルストーン制御とスケジュールの整合性検証におけるアーキテクチャ的アプローチ
プロジェクト管理ソフトウェアとしてのMicrosoft Projectは、その強力なスケジューリングエンジン(CPM: Critical Path Method)ゆえに、外部からVBAを用いて制御しようとする際、非常に繊細な挙動を示す。特に、大規模プロジェクトにおいて「特定の命名規則を持つマイルストーン」に対し、強制的な制約条件(Constraints)を付与し、かつその変更がスケジュール全体の整合性を破壊していないかを検証するプロセスは、単なるループ処理の積み重ねではない。
本稿では、オブジェクトのライフサイクル管理、COM参照の解放、そしてスケジューリングエンジンの論理的整合性を担保するための、プロフェッショナルな実装手法について詳述する。
1. スケジューリングエンジンの「不確実性」を制御する
MS Projectにおける制約条件、例えば `pjTaskFinishNoEarlierThan`(終了日指定)などは、エンジンの計算ロジックを直接的に上書きする。安易な自動適用は、クリティカルパスの不自然な歪みや、先行・後続関係の論理的破綻を招く。
シニアエンジニアが考慮すべきは、単に「値をセットする」ことではない。「制約を付与した後の再計算(Recalculate)が、意図したスケジュール・ドリフト(乖離)を引き起こしていないか」を、数学的に、あるいは論理的に検証する仕組みを構築することだ。
2. メモリ管理とCOMインターフェースの極限
VBAからMS Projectを操作する際、最も忌避すべきは「ゾンビプロセス」の発生である。大規模なタスクリスト(数万行規模)を処理する場合、オブジェクトの参照カウントが適切に制御されていないと、メモリリークが発生し、最終的にExcelやProjectのインスタンスが不安定化する。
`Set obj = Nothing` は基礎に過ぎない。実務レベルでは、`Marshal.ReleaseComObject` に相当する挙動を意識し、明示的にオブジェクトを解放する設計が求められる。
3. 実装:マイルストーン制約制御・検証エンジン
以下に、命名規則に基づいたマイルストーンへの制約適用と、その影響を検証するためのクラスモジュール設計のプロトタイプを示す。
Class Module: `clsMilestoneController`
Option Explicit
' Windows API: 処理時間の高精度計測用
Private Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long
Private mTaskCollection As Collection
Private mConstraintPattern As String
Private mTargetDate As Date
' 内部状態管理用
Private mIsErrorState As Boolean
Private Sub Class_Initialize()
Set mTaskCollection = New Collection
mIsErrorState = False
End Sub
''' <summary>
''' マイルストーン制御エンジンの初期化
''' </summary>
''' <param name="pattern">命名規則(例: "MS_*")</param>
''' <param name="fixedDate">適用する終了日</param>
Public Sub Initialize(ByVal pattern As String, ByVal fixedDate As Date)
mConstraintPattern = pattern
mTargetDate = fixedDate
End Sub
''' <summary>
''' プロジェクト内の対象タスクを走査し、制約を適用する
''' </summary>
Public Sub ApplyConstraints(ByRef targetApp As MSProject.Application)
Dim t As MSProject.Task
Dim startTime As Long
startTime = GetTickCount()
' スケジューリングエンジンの自動計算を一時停止(パフォーマンス最適化)
targetApp.Calculation = pjManual
On Error GoTo ErrorHandler
For Each t In targetApp.ActiveProject.Tasks
If Not t Is Nothing Then
' 命名規則によるフィルタリング(Like演算子による高速判定)
If t.Name Like mConstraintPattern Then
' マイルストーンであることを確認(期間が0)
If t.Duration = 0 Then
' 制約条件の適用: "Must Finish On" (pjTaskMustFinishOn)
' 強力な制約をかけることで、スケジュール変動を強制的に抑え込む
t.ConstraintType = pjTaskMustFinishOn
t.Finish = mTargetDate
' 変更履歴をコレクションに保持(検証用)
mTaskCollection.Add t
End If
End If
End If
Next t
' 再計算を実行
targetApp.Calculate
targetApp.Calculation = pjAutomatic
Debug.Print "Execution Time: " & (GetTickCount() - startTime) & " ms"
Exit Sub
ErrorHandler:
mIsErrorState = True
targetApp.Calculation = pjAutomatic
Err.Raise Err.Number, "clsMilestoneController", Err.Description
End Sub
''' <summary>
''' 制約適用後に、意図しないタスクの開始日が変動していないかを検証する
''' </summary>
Public Function ValidateScheduleIntegrity(ByRef targetApp As MSProject.Application) As Boolean
Dim t As MSProject.Task
Dim driftDetected As Boolean
driftDetected = False
' 本来は、適用前のスナップショットと比較すべきだが、
' ここでは制約をかけたマイルストーン自体の整合性と、
' 後続タスクへの「不自然な押し出し」を簡易的にチェックするロジックとする
For Each t In mTaskCollection
' 制約日と実際の終了日が一致しているか(エンジンの計算結果の検証)
If Abs(DateDiff("d", t.Finish, mTargetDate)) > 0 Then
driftDetected = True
Exit For
End If
Next t
ValidateScheduleIntegrity = Not driftDetected
End Function
''' <summary>
''' オブジェクトの明示的解放
''' </summary>
Public Sub Terminate()
' コレクション内の各オブジェクトに対する参照解除(重要)
Dim i As Long
For i = mTaskCollection.Count To 1 Step -1
Set mTaskCollection(i) = Nothing
Next i
Set mTaskCollection = Nothing
End Sub
Standard Module: `modMain`
Option Explicit
''' <summary>
''' メイン実行ルーチン
''' </summary>
Public Sub ExecuteMilestoneControl()
Dim projApp As MSProject.Application
Dim controller As clsMilestoneController
Dim targetDate As Date
' Projectインスタンスの取得
On Error Resume Next
Set projApp = MSProject.Application
On Error GoTo 0
If projApp Is Nothing Then
MsgBox "MS Projectが起動していません。", vbCritical
Exit Sub
End If
' 設定値(実務では外部ファイルやDBから取得)
targetDate = DateValue("2024-12-31")
' クラスのインスタンス化
Set controller = New clsMilestoneController
On Error GoTo Finalize
' 1. 初期化
controller.Initialize("MS_*", targetDate)
' 2. 制約適用
Debug.Print "Applying constraints..."
controller.ApplyConstraints projApp
' 3. 検証
Debug.Print "Validating integrity..."
If Not controller.ValidateScheduleIntegrity(projApp) Then
MsgBox "警告: 制約適用後にスケジュールの整合性が失われました。手動確認が必要です。", vbExclamation
Else
MsgBox "制約の適用と整合性検証が正常に完了しました。", vbInformation
End If
Finalize:
' 4. 徹底したメモリ解放
If Not controller Is Nothing Then
controller.Terminate
Set controller = Nothing
End If
If Err.Number <> 0 Then
MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
End If
End Sub
4. アーキテクチャの要諦:なぜこの設計なのか
4.1. 計算モードの制御 (`pjManual` vs `pjAutomatic`)
ループ内でタスクを一つ書き換えるたびにスケジューリングエンジンが走ると、計算量は $O(n^2)$ に近づき、パフォーマンスは壊滅的になる。`targetApp.Calculation = pjManual` によって、エンジンの介入を最小限に抑え、一括で `Calculate` を呼び出すのは、大規模プロジェクトを扱う際の鉄則である。
4.2. 制約の強度の選択
`pjTaskFinishNoEarlierThan`(終了日指定)は、エンジンの計算結果によって日付が後ろにずれることを許容する。一方で、`pjTaskMustFinishOn`(強制終了日)は、エンジンの計算結果を完全に無視する。実務において「マイルストーンは絶対に動かさない」という要求がある場合は、後者を選択せねばならないが、これは後続タスクに「負の遅延」を強制的に発生させるリスクを孕む。本コードでは、このリスクを検知するために `ValidateScheduleIntegrity` を設けている。
4.3. Windows APIによる計測
`GetTickCount` を用いた処理時間の計測は、単なるデバッグ用ではない。レガシーな環境や、ネットワークドライブ上の巨大な `.mpp` ファイルを扱う際、処理時間の異常な増大は、メモリリークやCOM参照の不備を検知するための重要なメトリクスとなる。
結論
VBAによるプロジェクト制御は、単なる自動化ツールではない。それは、スケジューリングエンジンという巨大な計算機に対する「命令系統の確立」である。オブジェクトのライフサイクルを掌握し、エンジンの論理的挙動を予測し、エラーを許容しない堅牢なコードを書くこと。それこそが、レガシーな環境から最新のエンタープライズ・アーキテクチャまでを生き抜く、シニアエンジニアの責務である。
