【テクニカル・上級編】【中級者向け】プロジェクトファイル内のリソース割り当て状況を保存前にチェックするバリデーションツール – Project VBA解析バイブル

スポンサーリンク

Project VBAを掌握する極限の知見

MS Projectにおける保存前イベントの掌握:リソース過負荷バリデーションの極意

プロジェクトマネジメントの現場において、データ整合性の崩壊は致命傷となる。特にMicrosoft Project(以下、MS Project)を用いた大規模スケジューリングでは、リソースの過負荷(オーバーアロケーション)や、担当者がアサインされていない「野良タスク」の放置が、プロジェクト全体の破綻を引き起こす。

多くの開発者は、エラーが発生した「後」に対処しようとする。しかし、シニアアーキテクトであれば、「不正な状態のデータがストレージに書き込まれること自体を物理的に阻止する」設計を選ぶべきだ。

今回は、MS ProjectのVBA環境において、ファイル保存時(`BeforeSave`イベント)にプロジェクト内のリソース割り当て状況を徹底的にスキャンし、条件違反があれば保存を強制中断する高信頼性バリデーションツールの実装手法を解説する。

1. MS Projectイベントアーキテクチャの暗部

Excel VBAにおけるWorkbookイベントとは異なり、MS Projectのイベントハンドリングは、Applicationオブジェクトのライフサイクルと密に結合している。ここで誤った実装をすると、メモリリークやイベントの多重発火、最悪の場合アプリケーションの強制終了を引き起こす。

保存をインターセプトするには、`MS Project`の `App` クラスモジュールを用いたイベントフックが不可欠である。

実装の全体像

1. クラスモジュール (`EventClass`): `MSProject.App`のイベントを継承し、`BeforeSave`をキャプチャする。
2. 標準モジュール (`Module_Manager`): クラスのインスタンスをグローバルに保持し、イベントの有効化・無効化を制御する。

2. 実装コード:堅牢な保存前バリデーションエンジン

以下のコードは、実務の現場でそのまま稼働させられるプロダクション品質のコードである。オブジェクトの明示的な解放、エラーハンドリング、そしてパフォーマンスを意識したループ構造を取り入れている。

クラスモジュール:`EventClass`

‘ Option Explicitの強制による暗黙の変数の排除
Option Explicit

‘ WithEventsキーワードを用いてMS Projectのアプリケーションイベントを捕捉
Public WithEvents PrjApp As MSProject.Application

‘====================================================================
‘ イベント名: BeforeSave
‘ 概要: プロジェクトが保存される直前に発火し、リソース状況を検証する
‘====================================================================
Private Sub PrjApp_BeforeSave(ByVal pj As MSProject.Project, ByRef Cancel As Boolean)
On Error GoTo ErrorHandler

Dim tsk As Task
Dim资源CheckPassed As Boolean
Dim unassignedCount As Long
Dim overAllocatedCount As Long
Dim msg As String

‘ 初期化
unassignedCount = 0
overAllocatedCount = 0

‘ パフォーマンス最適化:画面描画と自動計算を一時停止(Projectでは直接サポートされないが論理的整合性を維持)
‘ ※MS Projectのオブジェクトモデルでは直接App.ScreenUpdatingはないため、タスク走査を効率化する

‘ 1. 未割り当てタスクの検出(サマリータスクとマイルストーンを除外するのが実務的)
For Each tsk In pj.Tasks
If Not tsk Is Nothing Then
‘ 外部タスク、サマリータスク、マイルストーンを除外して実作業タスクのみ評価
If Not tsk.Summary And tsk.Milestone = False And tsk.ExternalTask = False Then
If tsk.ResourceNames = “” Then
unassignedCount = unassignedCount + 1
End If
End If
End If
Next tsk

‘ 2. リソース過負荷(オーバーアロケーション)の検出
Dim res As Resource
For Each res In pj.Resources
If Not res Is Nothing Then
‘ 予約超(Peak UnitsがMax Unitsを超過しているか、あるいは標準的な過負荷判定)
‘ MS Projectの組込プロパティであるOverAllocatedを評価
If res.OverAllocated Then
overAllocatedCount = overAllocatedCount + 1
End If
End If
Next res

‘ 3. バリデーション判定
If unassignedCount > 0 Or overAllocatedCount > 0 Then
msg = “【保存中断】プロジェクトの整合性チェックに失敗しました。” & vbCrLf & vbCrLf & _
“・未割り当てタスク数: ” & unassignedCount & ” 件” & vbCrLf & _
“・過負荷リソース数: ” & overAllocatedCount & ” 件” & vbCrLf & vbCrLf & _
“問題を解消してから再度保存してください。”

MsgBox msg, vbCritical + vbApplicationModal, “Project バリデーションシステム”

‘ 保存をキャンセル
Cancel = True
Else
‘ 正常な場合は保存を許可(Cancel = False のまま)
Cancel = False
End If

CleanUp:
‘ オブジェクト参照の明示的破棄(メモリ最適化)
Set tsk = Nothing
Set res = Nothing
Exit Sub

ErrorHandler:
MsgBox “バリデーション処理中に予期せぬエラーが発生しました: ” & Err.Description, vbCritical
Cancel = True ‘ 安全のためエラー時は保存をブロック
Resume CleanUp
End Sub

標準モジュール:`Module_Manager`

Option Explicit

‘ イベントを監視するクラスのインスタンスを保持するグローバル変数
Public MonitorApp As EventClass

‘====================================================================
‘ プロシージャ名: InitEventMonitor
‘ 概要: イベント監視を開始するエントリーポイント
‘====================================================================
Public Sub InitEventMonitor()
If MonitorApp Is Nothing Then
Set MonitorApp = New EventClass
Set MonitorApp.PrjApp = Application
MsgBox “プロジェクトの保存前バリデーション監視が有効化されました。”, vbInformation, “システム通知”
Else
MsgBox “監視は既に稼働しています。”, vbInformation, “システム通知”
End If
End Sub

‘====================================================================
‘ プロシージャ名: StopEventMonitor
‘ 概要: イベント監視を停止し、メモリを解放する
‘====================================================================
Public Sub StopEventMonitor()
If Not MonitorApp Is Nothing Then
Set MonitorApp.PrjApp = Nothing
Set MonitorApp = Nothing
MsgBox “プロジェクトの保存前バリデーション監視を停止しました。”, vbInformation, “システム通知”
End If
End Sub

3. シニアエンジニアが押さえるべきアーキテクチャの急所

上記のコードを単に動かすだけでは、プロフェッショナルとは言えない。以下の3つの「極限の知見」を理解し、現場に適用する必要がある。

A. オブジェクトのライフサイクルとメモリマネジメント

VBAのガベージコレクションは極めて脆弱である。特に`For Each`ループ内で生成・参照される `Task` や `Resource` オブジェクトは、スコープを抜けても即座にメモリから解放されるとは限らない。

  • 対策: ループ内で明示的に `Set tsk = Nothing` を行うことはないが、サブルーチン終了時、あるいは処理の切れ目でグローバル参照やオブジェクト変数(`MonitorApp`など)を確実に破棄(`Nothing`代入)する設計を徹底する。これにより、Project本体のメモリリークやアドイン起因のクラッシュを防ぐ。

B. パフォーマンスの最適化(大規模プロジェクト対策数千タスクへの対応)

数千〜数万行に及ぶエンタープライズレベルのスケジュールにおいて、`For Each tsk In pj.Tasks` を単純に回すと、COMのマーシャリングオーバーヘッドにより数秒から数十秒のフリーズが発生する。

  • 実務的アプローチ: 本当に厳密にチェックすべきは「ベースライン設定済み、かつアクティブなタスク」のみである。必要に応じて `Filter` オブジェクトや、SQLライクな条件分岐を組み込み、走査対象の母数を最小限に絞り込むチューニングが求められる。

C. 例外時のフェイルセーフ(Fail-Safe)設計

バリデーションスクリプト自体がバグっていたり、予期せぬデータ構造(破損したプロジェクトファイルなど)に遭遇してエラー(`Err`)が発生した場合、「エラーを無視して保存させてしまう」という実装は最悪の悪手である。

  • 鉄則: エラーハンドラ内では必ず `Cancel = True` を設定し、「システムエラー時はデータを保存させない」という保守的なフェイルセーフを貫くこと。データの破損よりも、保存できないことのほうが復旧コストは圧倒的に低い。

4. システム間連携を見据えた拡張性

このバリデーションツールは、単なる「ローカルの保存抑止」にとどまらない。
将来的には、ここで検出された過負荷データや未割り当てデータを、JSON形式等にシリアライズして社内の基幹システム(ERPや工数管理API)へ非同期送信し、プロジェクトマネージャのSlackやTeamsへ自動アラートを飛ばすパターンの前段としても機能する。

VBAを単なる「マクロ記録の延長」として扱う時代は終わった。アーキテクチャを理解し、リソースとメモリを支配した者だけが、真に堅牢なEnterprise VBAシステムを構築できる。

タイトルとURLをコピーしました