【テクニカル・上級編】【上級プロ】Project VBAにおける「エラーハンドリング」の設計:例外発生時のロールバック処理 – Project VBA解析バイブル

スポンサーリンク

Project VBAを掌握する極限の知見:Project VBAにおける「エラーハンドリング」の設計:例外発生時のロールバック処理

Microsoft ProjectのVBA開発において、アマチュアとシニアエンジニアを分ける決定的な境界線がある。それは「正常系をいかに美しく書くか」ではなく、「異常系において、いかに美しく、そして確実にロールバックを完遂するか」だ。

ExcelやWordのVBAであれば、最悪の場合、ファイルを保存せずに閉じれば被害は局所化される。しかし、MS Projectは違う。エンタープライズ環境において、複数リソースの工数、複雑な依存関係、コスト、そしてサードパーティ製アドインやERP連携のミドルウェアが絡み合うプロジェクトプランを破壊されたときの損失は計り知れない。

今回は、Projectオブジェクトモデルの特性、メモリ管理の罠、そしてトランザクション(ACID特性)不在のVBA環境下において、いかにして「堅牢なロールバック機構」を構築するか、その極限の知見を授ける。

1. Project VBAにおける「トランザクション不在」という絶望

リレーショナルデータベースであれば、`BEGIN TRANSACTION` からの `ROLLBACK` が標準装備されている。しかし、VBAにはそれがない。特にMS Projectのオブジェクトモデル(`Project`, `Task`, `Resource`)は、一度プロパティを変更し、タスクを追加・削除すると、それは即座にメモリ上のプロジェクトデータベースに反映される。

途中でエラーが発生した際、`On Error GoTo` でハンドリングするだけでは、「半端にタスクが追加され、先行タスクのリンクが千切れ、リソースの割当が崩壊したゾンビ状態のプロジェクトファイル」が残されることになる。

これを防ぐためには、以下の3階層の防衛線を構築しなければならない。

1. 事前退避(Shadow Copy / 仮想スナップショット)
2. 実行中の例外捕捉と即時切断(Fail-Fast & Safe Abort)
3. 明示的なオブジェクト解放とメモリ最適化(Garbage Collectionの強制)

2. 実装パターン:トランザクション型エラーハンドリングの全容

以下のコードは、数千件規模の大規模スケジュールを一括操作するバッチ処理を想定した、実戦投入可能な最高峰のテンプレートである。一時ファイルによるシャドウコピー方式を採用し、致命的エラー発生時には一切の改変を無かったことにする。

Option Explicit

‘ Win32 API: 処理速度向上のための画面描画・イベント抑止(オプション)
If VBA7 Then
Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
End If

Public Sub ExecuteTransactionalProjectUpdate()
Dim appPrj As MSProject.Application
Dim prjTarget As MSProject.Project
Dim strTempPath As String
Dim blnIsTransactionActive As Boolean

‘ —————————————————-
li Initialization & Fail-Fast Guard
‘ —————————————————-
On Error GoTo ErrorHandler

Set appPrj = ActiveProject.Application
If appPrj.Projects.Count = 0 Then
Err.Raise vbObjectError + 1000, “Transaction”, “処理対象のプロジェクトが開かれていません。”
End If

Set prjTarget = appPrj.ActiveProject

‘ パフォーマンスと予期せぬUI干渉の排除
appPrj.ScreenUpdating = False
appPrj.Calculation = pjCalculationManual ‘ 手動計算モードで整合性崩壊を防ぐ

‘ —————————————————-
2. シャドウコピー(ロールバック用退避)の作成
‘ —————————————————-
strTempPath = Environ$(“TEMP”) & “\PrjRollback_” & Format(Now, “yyyymmdd_hhmmss”) & “.mpp”

‘ 現在の状態を完全保存(ファイルベースのトランザクションログ代用)
prjTarget.SaveAs FileName:=strTempPath, FileFormat:=pjFileMPP
blnIsTransactionActive = True

‘ —————————————————-
3. メインビジネスロジック(破壊的変更の実行)
‘ —————————————————-
‘ ※ここにタスクの大量追加、カスタムフィールドの書き換え、リンク構築などを記述
Call ProcessDangerousOperations(prjTarget)

‘ —————————————————-
4. 正常終了処理(コミット)
‘ —————————————————-
blnIsTransactionActive = False

‘ 一時ファイルのクリーンアップ
If Dir(strTempPath) <> “” Then
Kill strTempPath
End If

GoTo CleanUp


‘ —————————————————-
5. 異常終了処理(ロールバック)
‘ —————————————————-
Dim lngErrNum As Long: lngErrNum = Err.Number
String strErrDesc As String: strErrDesc = Err.Description

If blnIsTransactionActive Then
‘ 変更された現在の破損可能性のあるプロジェクトを破棄し、退避ファイルから復元
On Error Resume Next ‘ ロールバック中の二重エラー防止

If Dir(strTempPath) <> “” Then
‘ 現在のアクティブプロジェクトを保存せずに閉じる
prjTarget.Close pjDoNotSave

‘ シャドウコピーを元の名前で再オープン(あるいは上書き復元)
Set prjTarget = appPrj.FileOpenEx(strTempPath)

‘ 一時ファイルの削除
Kill strTempPath
End If

On Error GoTo 0
MsgBox “致命的なエラーが発生しました。プロジェクトは変更前の状態にロールバックされました。” & vbCrLf & _
“エラー内容: ” & strErrDesc, vbCritical, “トランザクション異常終了”
Else
MsgBox “エラーが発生しましたが、トランザクション開始前の段階でした。” & vbCrLf & _
“エラー内容: ” & strErrDesc, vbExclamation, “警告”
End If

CleanUp:
‘ —————————————————-
6. 厳格なメモリ解放と環境復元(Finalizer)
‘ —————————————————-
On Error Resume Next
appPrj.Calculation = pjCalculationAutomatic
appPrj.ScreenUpdating = True

‘ オブジェクトの明示的解放(メモリリーク・参照カウントのデクリメント)
Set prjTarget = Nothing
Set appPrj = Nothing

If lngErrNum <> 0 Then
‘ 必要に応じてログ出力基盤へ転送
End If
End Sub

Private Sub ProcessDangerousOperations(ByRef prj As MSProject.Project)
‘ 意図的なエラーテストまたは複雑なタスク操作
Dim t As MSProject.Task

‘ 例: 大量のタスク追加ループ
For Each t In prj.Tasks
If Not t Is Nothing Then
‘ ここでカスタムプロパティの不正代入や例外が起きると想定
‘ t.Text1 = “Updated”
End If
Next t

‘ 意図的エラーの発生テスト
‘ Err.Raise vbObjectError + 9999, “Process”, “バッファオーバーフローまたは外部API連携失敗”
End Sub

3. シニアエンジニアが知るべき「メモリ最適化」と「オブジェクトの寿命管理」

VBAのガーベジコレクションは信用するな。特にMicrosoft ProjectのCOMオブジェクトモデルは、参照の解放漏れがそのままメモリリークや、最悪の場合`RPC_E_DISCONNECTED`(リモートプロシージャコールが切断されました)という、VBAセッション全体のクラッシュを引き起こす。

オブジェクト変数の即時破棄 (`Set obj = Nothing`)

ループ内で `Task` や `Resource` オブジェクトを走査・生成する際、変数のスコープ管理を誤ると、Projectの内部ポインタがメモリ上に保持され続け、メモリフットプリントが肥大化する。
数千行のWBSを処理する際は、ループの各イテレーション、あるいは処理の節目で確実に `Set obj = Nothing` を実行し、COMコンポーネントの参照カウントを意図的にゼロに落とさなければならない。

`Application.Calculation` の制御

MS Projectはデフォルトで、タスクが1つ追加・変更されるたびに全体のスケジュール計算(CPM:クリティカルパス法に基づく再計算)を走らせる。
これを有効にしたままVBAで1,000件のタスクを追加すると、エラーハンドリング以前にパフォーマンスが破綻するか、計算中の割り込みによってデータ構造が破損する。
「処理の最初に手動計算モードへ切り替え、例外発生時および終了時に必ず自動計算へ戻す」。これが鉄則である。

4. レガシー環境・外部システム連携を見据えた堅牢性

エンタープライズ領域では、Project単体で完結することは稀だ。SAP、Oracle、あるいは独自基幹システム(DB)との連携において、VBAは「インテグレーションの橋渡し」として使われる。

  • トランザクションの二重管理の回避:

外部DB側のトランザクションと、MS Project側のシャドウコピーによるロールバックタイミングがズレると、システム間に致命的な整合性の乖離(スプリットブレイン)が生じる。外部連携を伴う場合は、必ず「外部システムの処理が完全に成功したこと」を確認した後に、Project側のコミット(一時ファイルの削除)を行うこと。

  • Windows APIによるプロセス監視:

大規模アドインや外部COMサーバーを呼び出す際、応答なし(フリーズ)状態に陥った場合のエスケープルートとして、必要に応じてWin32 APIを駆使したタイムアウト監視やメモリ使用量のモニタリングを組み込む設計思想が、プロフェッショナルには求められる。

結言

Project VBAにおけるエラーハンドリングとは、単なる「 `On Error Resume Next` の乱用によるエラー隠蔽」ではない。「予測不可能な障害の嵐の中でも、ビジネス資産であるスケジュールデータの完全性を守り抜くための防衛エンジニアリング」である。

ここで示したシャドウコピーと厳格なオブジェクトライフサイクル管理のパターンをあなたのアーキテクチャに組み込むことで、レガシーとモダンが混在する過酷な現場であっても、揺るぎない堅牢性を手に入れることができるはずだ。コードの美しさは、異常系をいかに支配しているかに宿る。

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