【上級プロ】Project VBAにおける「エラーハンドリング」の設計:例外発生時のロールバック処理
Microsoft ProjectのVBA開発において、アマチュアとプロの決定的な違いはどこにあるか知っているか?
それは「ハッピーパス(正常系)」しか書けないか、それとも「ダークサイド(異常系)」を完全掌握しているかだ。
Excel VBAの感覚で `On Error Resume Next` を乱発し、エラーを握りつぶすコードを書く者は、今すぐキーボードを置きたまえ。Projectのオブジェクトモデルは、Excelのペラペラのセル群とはわけが違う。タスク、リソース、割り当て、そしてスケジュール計算エンジンが複雑に絡み合う、極めて緻密な巨大データベースなのだ。
途中でマクロがクラッシュすれば、スケジュールは歪み、コスト計算は崩壊し、ファイルは破損する。実務の現場でそんな爆弾をばら撒くわけにはいかない。
今回は、Project VBAにおいて「トランザクション的なロールバック処理」を実現し、例外発生時にプロジェクトを完璧に元の正常な状態へ復元するための極限の知見を授けよう。
—
1. なぜProject VBAのエラーハンドリングは難しいのか?
Excelであれば、最悪の場合でもシート全体をクリアしてやり直せば済む。しかし、MS Projectには以下の特有の重みがある。
- 自動スケジュール計算エンジン(Calculation Engine)の存在:
ひとつのタスクの開始日を変更した瞬間、依存関係(リンク)を通じて数百のタスクの日付が連鎖的に再計算される。途中でエラーが起きた場合、この「連鎖」の途中で処理が止まるため、データムラが最も発生しやすい。
- アンドゥ(Undo)スタックの限界:
VBAからの操作は、ユーザーのUI操作とは異なり、単純な `Ctrl + Z` では綺麗に巻き戻せないケースが多い。特に外部データベース(SQL ServerやPWA)との連携時、中途半端なコミットは致命傷になる。
したがって、「VBAの処理を開始する前の状態をスナップショットとして保持し、例外発生時には手動で完全に巻き戻す(ロールバックする)」 という強固な設計が不可欠となる。
—
2. 設計思想:安全なトランザクションの4カ条
プロダクションコードを書くにあたり、以下のアーキテクチャを頭に叩き込んでおいてほしい。
1. 計算の非同期化(一時停止): 処理中の無駄な再計算を防ぐため、一時的に手動計算モードにするか、イベントを制御する。
2. ベースライン・スナップショット: 必要に応じて一時的なベースラインやプロジェクト自体のコピーを退避領域とする。
3. 厳格なエラー伝播: 発生したエラーを隠蔽せず、キャッチした上で確実にロールバック処理へルーティングする。
4. 終了時の確実なクリーンアップ: 成功しようが失敗しようが、アプリケーションの設定(画面描画や計算モード)を必ず元の状態に戻す。
—
3. 実装パターン:ロールバック機構付きプロダクションコード
それでは、実務でそのまま使える堅牢なコードを提示しよう。
このコードは、一連のタスク追加・更新処理中にエラーが発生した場合、プロジェクトを処理実行前の状態にロールバックするテンプレートだ。
Option Explicit
‘ ==============================================================================
‘ 処理名: 堅牢なタスクバッチ更新プロシージャ(ロールバック機能付き)
‘ 概要: 複数のタスク操作をトランザクションとして扱い、失敗時は状態を復元する
‘ ==============================================================================
Public Sub ExecuteTransactionWithRollback()
Dim prj As Project
Set prj = ActiveProject
‘ 状態管理フラグ
Dim isSuccess As Boolean
isSuccess = False
‘ パフォーマンスと整合性維持のための環境退避
Dim origCalcMode As Long
origCalcMode = Application.Calculation
‘ 【重要】エラーハンドラーの有効化
On Error GoTo ErrorHandler
‘ 1. トランザクション前処理(環境のロックと手動計算化)
Application.Calculation = pjManual
Application.ScreenUpdating = False
‘ ★ ここで現在の状態を復元するための「バックアップ」を作成
‘ 実務では一時ファイルへの保存、または固有IDによる変更前データの退避を行います。
‘ 今回はシンプルかつ確実に元に戻すため、処理前のプロジェクトを一時ファイルとして退避する手法をとります。
Dim tempFilePath As String
tempFilePath = Environ(“TEMP”) & “\Project_Rollback_” & Format(Now, “yyyymmddhhnnss”) & “.mpp”
prj.SaveAs Name:=tempFilePath
‘ ==============================================================================
‘ 【メイン処理ブロック】(ここに実際の業務ロジックを記述)
‘ ==============================================================================
Dim t As Task
‘ わざとエラーを誘発するような処理、または複雑なタスク操作
For Each t In prj.Tasks
If Not t Is Nothing Then
‘ 例:カスタムフィールドの更新やタスク名の変更
t.Text1 = “Updated by Macro”
‘ テスト的に特定の条件でエラーを発生させる場合
‘ If t.Name = “ErrorTask” Then Err.Raise 9999, “BusinessLogic”, “意図的な業務エラー発生”
End If
Next t
‘ 追加の処理…
Dim newTask As Task
Set newTask = prj.Tasks.Add(Name:=”新規検証タスク”)
‘ newTask.Duration = “5d” ‘ ここで何らかの不正値を入れるとエラーになる可能性がある
‘ ==============================================================================
‘ 処理が正常に完了した場合のフラグ立て
isSuccess = True
GoTo CleanUp
ErrorHandler:
‘ ==============================================================================
‘ 【例外処理(ロールバック)ブロック】
‘ ==============================================================================
Dim errNum As Long, errDesc As String
errNum = Err.Number
errDesc = Err.Description
‘ ユーザーへの通知とログ記録
MsgBox “エラーが発生したため、変更をロールバックします。” & vbCrLf & _
“エラー番号: ” & errNum & vbCrLf & _
“詳細: ” & errDesc, vbCritical, “トランザクション異常終了”
‘ ロールバックの実行:退避していたファイルを開き直すことで強制的に巻き戻す
If Dir(tempFilePath) <> “” Then
‘ 現在の変更を保存せずに閉じる(読み取り専用や変更破棄)
prj.Saved = True ‘ 閉じる際の「保存しますか?」ダイアログを抑制
FileClose pjDoNotSave
‘ バックアップから復元
FileOpen Name:=tempFilePath, ReadOnly:=False
‘ バックアップファイルを削除
Kill tempFilePath
End If
CleanUp:
‘ ==============================================================================
‘ 【クリーンアップブロック】
‘ ==============================================================================
‘ 環境を元の状態に復元
Application.Calculation = origCalcMode
Application.ScreenUpdating = True
‘ 正常終了時のバックアップファイル後片付け
If isSuccess Then
If Dir(tempFilePath) <> “” Then
Kill tempFilePath
End If
MsgBox “すべての処理が正常に完了し、コミットされました。”, vbInformation, “成功”
End If
Exit Sub
End Sub
—
4. プロジェクト開発・DB連携における「実務上の罠」
上記のコードを見て、「一時ファイルで丸ごと保存・復元するなんて乱暴ではないか?」と思った鋭い読者もいるだろう。
しかし、Project VBAにおいて、数千行に及ぶWBSの整合性をコード側だけで一つひとつ逆順にロールバック(タスクの削除、リンクの再構築、コストの再設定など)させようとすれば、ロールバック処理自体のバグによってさらに悲惨な状態を招く。
ファイル単位でのスナップショット(あるいはデータベース連携時のトランザクション制御)こそが、最も確実かつ保守性の高いアプローチなのだ。
データベース(SQL Server / PWA)連携時の注意点
もしこのVBAが、Microsoft Project ServerやProject Online、あるいは社内ニッチなSQL ServerデータベースとODBC/REST API経由で連携している場合、話はさらにシビアになる。
1. ADOトランザクションの併用:
外部DBへ書き込みを行う場合は、VBA側だけでなく、SQL側の `BEGIN TRANSACTION` / `COMMIT` / `ROLLBACK` を明示的にコントロールしなければならない。
2. Project側のデータとDB側の不整合:
「Projectファイル側はロールバックできたが、外部DBにはすでに書き込みが走っていた」という事態を防ぐため、外部DBへの書き込みはすべての前処理と検証が完了した「最後のフェーズ」にのみ配置するのが鉄則である。これを「遅延書き込みパターン」と呼ぶ。
—
5. チーフアーキテクトからの最終提言
コードが動くことと、プロダクション環境で耐えうることは全く次元が違う。
あなたが組むマクロは、現場のプロジェクトマネージャーやエンジニアの大切なスケジュール、ひいては企業の資産を預かっている。例外処理を面倒くさいからと後回しにするプログラマは、プロを名乗る資格がない。
今回伝授した「環境の退避」「スナップショットによるロールバック」「確実に実行されるクリーンアップ」の3点セットを型として体に染み込ませたまえ。どんな巨大なプロジェクトファイルを相手にしようとも、あなたの書いたコードは決してシステムを破壊せず、美しく安全に立ち回るはずだ。
