はじめに:なぜ「保存前のバリデーション」がプロジェクトの命運を分けるのか
プロジェクトマネジメントにおいて、MS Projectのスケジュールファイルは単なるガントチャートの綺麗なお絵描きではない。コスト、リソースのキャパシティ、そして組織の稼働限界が緻密に織り込まれた「実行計画のデータベース」である。
しかし、現場のPMやリソース担当者が陥りがちな罠がある。それは、「とりあえずタスクを突っ込み、後から調整しようとしてそのまま忘れる」というヒューマンエラーだ。
結果として、オーバーアロケーション(過負荷)を起こしたリソースが放置され、アサイン漏れのクリティカルタスクが隠蔽されたまま上位組織やクライアントにファイルが共有される。この腐敗したデータを起点にプロジェクトが回り始めれば、現場が崩壊するのは時間の問題だ。
「ファイル保存時に、システムが自動でリソースの健全性を検査し、不備があれば保存をブロックまたは警告する」
この仕組みをProject VBA(Visual Basic for Applications)で構築することこそ、場当たり的なプロジェクト管理から組織を防衛するための、シニアアーキテクト必須の布石となる。
今回は、MS Projectのイベント駆動モデルをハックし、「保存前(BeforeSave)」のフックを利用した極限まで堅牢なバリデーションツールの実装方法を伝授する。
—
1. アーキテクチャ設計:なぜ通常のVBAマクロでは不十分なのか
多くの初級・中級プログラマは、「ボタンを押したらチェックするマクロ」を作りがちだ。しかし、これでは意味がない。人間は「保存する」という最終防衛ラインの直前でしか、自身の作業ミスを振り返らないからだ。
真に実用的なツールを作るためには、以下の要件を満たすアーキテクチャが必要となる。
1. イベントのフック(App_BeforeSave):
ユーザーが「上書き保存」または「名前を付けて保存」を実行した瞬間をキャッチし、VBAの実行フローに割り込む。
2. トランザクションの制御(Cancel = True):
バリデーションエラーを検知した場合、保存処理そのものをキャンセルし、不完全なデータがストレージに書き込まれるのを物理的に阻止する。
3. パフォーマンスの担保:
数千行に及ぶエンタープライズ規模のプロジェクトファイルであっても、数秒以内で検査を完了させるためのオブジェクト走査の最適化。
—
2. プロダクションコード実装
以下のコードを、MS ProjectのVBE環境における「ThisProject」モジュール、および標準モジュールに配置する。
今回は、実務で最も要望が多い以下の2点をバリデーションの条件とする。
- 過負荷(Over-allocated)リソースの検知:標準の稼働時間を超えているリソースが存在するか。
- 未割り当て(Unassigned)タスクの検知:リソースがアサインされていないアクティブなタスクが存在するか(マイルストーンやサマリータスクは除外)。
① イベントハンドラの設置(ThisProject モジュール)
Option Explicit
‘ Applicationオブジェクトのイベントを捕捉するための宣言
Private WithEvents oApp As Application
Private Sub Project_Open(ByVal Sv As Project)
‘ プロジェクトオープン時にアプリケーションイベントを有効化
Set oApp = Me.Application
End Sub
Private Sub Project_Activate(ByVal Sv As Project)
‘ アクティブ化された際にも確実に対象を捕捉
Set oApp = Me.Application
End Sub
‘ 【最重要】保存直前に発火するイベント
Private Sub oApp_BeforeSave(ByVal Pv As Project, ByRef Cancel As Boolean)
On Error GoTo ErrorHandler
Dim isValid As Boolean
‘ バリデーション実行
isValid = ValidateProjectState(Pv)
If Not isValid Then
‘ ユーザーに保存中断を通知
Dim userRes As VbMsgBoxResult
userRes = MsgBox(“【エラー】プロジェクトの整合性チェックに失敗しました。” & vbCrLf & _
“過負荷リソースまたは未割り当てのタスクが存在します。” & vbCrLf & vbCrLf & _
“このまま強制保存しますか?(「いいえ」を選択すると保存を中止します)”, _
vbCritical + vbYesNo + vbDefaultButton2, “Project Validation Guard”)
If userRes = vbNo Then
‘ 保存処理をキャンセル
Cancel = True
MsgBox “保存処理を中止しました。問題を修正してください。”, vbInformation, “中止”
End If
End If
Exit Sub
ErrorHandler:
MsgBox “バリデーション処理中に予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “システムエラー”
Cancel = True ‘ 安全のためエラー時は保存をブロック
End Sub
② バリデーションロジックの実装(標準モジュール:`ModValidator`)
Option Explicit
Public Function ValidateProjectState(ByVal prj As Project) As Boolean
Dim t As Task
Dim r As Resource
Dim hasError As Boolean
hasError = False
‘ —————————————————-
‘ 1. 未割り当てタスクのチェック
‘ —————————————————-
For Each t In prj.Tasks
‘ 存在し、有効なタスク(サマリー、マイルストーン、外部タスクを除外)
If Not t Is Nothing Then
If Not t.Summary And t.Milestone = False And t.Active And t.WBS <> “” Then
‘ リソースがアサインされていない(ResourceNamesが空)
If Trim(t.ResourceNames) = “” Then
‘ 実際の工数(Work)が0より大きい場合のみ検知対象とする
If t.Work > 0 Then
hasError = True
Debug.Print “未割り当て検知: ID=” & t.ID & ” Name=” & t.Name
End If
End If
End If
End If
Next t
‘ —————————————————-
‘ 2. リソース過負荷(オーバーアロケーション)のチェック
‘ —————————————————-
For Each r In prj.Resources
If Not r Is Nothing Then
If r.OverAllocated Then
hasError = True
Debug.Print “過負荷検知: Resource=” & r.Name
End If
End If
Next r
‘ エラーがなければ True を返す
ValidateProjectState = Not hasError
End Function
—
3. コードの解説とアーキテクトの視点
ここで実装したコードのポイントを、プロフェッショナルの視点から解説する。
`WithEvents` によるイベントのライフサイクル管理
`ThisProject` 内で `Private WithEvents oApp As Application` を宣言することにより、Projectアプリケーション全体で発生するイベント(`BeforeSave` など)をキャッチできる。これにより、個別のファイルに依存せず、常にバックグラウンドで監視の網を張ることが可能になる。
`Cancel = True` によるトランザクション制御
VBAのイベントプロシージャにおける `ByRef Cancel As Boolean` は、データベースのロールバックに等しい強力な機能だ。条件不一致の際に `Cancel = True` を明示することで、不整合な状態のスケジュールがサーバーやローカルディスクに書き込まれるのを未然に防ぐ。
冗長なループ処理の回避とフィルタリング条件
数千行のタスクを走査する際、すべてのタスクを無条件で判定するとパフォーマンスが著しく低下する。今回のコードでは以下のように厳格なフィルターをかけ、負荷を極限まで削ぎ落としている。
- `Summary = True`(親タスク・サマリー)の除外
- `Milestone = True`(マイルストーン)の除外
- `Work > 0`(実質的な工数が設定されているもの)に限定
—
4. 運用上の注意点とさらなる高みへ
このツールを実際の組織に導入する際は、以下の運用ガバナンスに注意してほしい。
1. マクロ有効ファイル形式(.mpp)の強制:
当然ながら、このコードはマクロが含まれるため、拡張子は `.mpp` ではなく `.mpp`(マクロ有効)またはエンタープライズ環境であればGlobal.mptへの組み込みを検討する必要がある。
2. 「強制保存」のバイパス権限:
今回のコードでは `MsgBox` でユーザーに選択肢(Yes/No)を与えているが、厳格なプロジェクトでは「Yes」を選ばせない(=完全ブロック)設計にすべき場合もある。プロジェクトの成熟度に応じてコードの厳格さを調整してほしい。
おわりに
業務自動化やツール開発の本質は、「楽をすること」ではなく、「組織のミスを構造的に排除すること」にある。
今回紹介した保存前バリデーションの仕組みを導入することで、プロジェクトの品質は劇的に向上し、管理工数は確実に削減されるだろう。
プログラミングとは、ただ動くだけのコードを書くことではない。組織のルールをコードに宿し、事業を守ることなのだ。
