【極限の堅牢性】FSOにおけるI/Oエラーの完全掌握:0x800A…を制する高度な例外復旧アーキテクチャ
プロフェッショナルの仕事と、単なる「動くスクリプト」の決定的な違いはどこにあるか?
それは、「正常系がどれだけ綺麗に動くか」ではなく、「異常系に直面した際、システムがいかにエレガントに、かつ予測可能な挙動を示すか」という一点に集約される。
多くの開発者が、VBScriptでファイル操作(FSO: FileSystemObject)を実装する際、安易に `On Error Resume Next` を使い、エラーを握りつぶす。これはエンジニアリングにおける「怠慢」であり、後に大規模なデータ欠落や、原因不明のジョブ停止という形で、プロジェクトに致命的な代償を払わせることになる。
本記事では、FSO操作において避けては通れないI/Oエラーの正体を解剖し、単なるエラー検知を超えた、「自動リトライ」「スキップ」「管理者退避(Quarantine)」を組み合わせた高度な例外ハンドリング・パターンを伝授する。
1. 敵を知る:FSOエラーコードの真実
FSOが投げるエラーは、多くの場合、COM(Component Object Model)のエラーコード(HRESULT)に由来する。これらを正しく分類できなければ、適切な復旧ロジックを設計することは不可能だ。
実務で遭遇する主要なエラーを、その性質ごとに分類する。
A. 一時的エラー(Transient Errors)
これらは「再試行(Retry)」によって解決する可能性がある。
- 0x800A0046 (Permission Denied / 書き込み禁止)
- 原因: 他のプロセス(ウイルス対策ソフトの走査や、ユーザーによるファイルオープン)がファイルをロックしている。
- 戦略: 数秒のウェイトを挟んだリトライ。
B. 構造的エラー(Structural Errors)
これらは「スキップ(Skip)」または「ログ記録」が必要な、対象ファイル固有の問題である。
- 0x800A004B (Path Not Found / パス未発見)
- 原因: ネットワークドライブの切断、あるいは操作中にディレクトリが削除された。
- 戦略: ログに記録し、そのファイル・フォルダの処理をスキップ。
- 0x800A000E (File Not Found / ファイル未発見)
- 原因: 処理のタイミング(Race Condition)により、存在を確認した直後にファイルが移動・削除された。
- 戦略: 存在確認の再試行、またはスキップ。
C. 致命的エラー(Fatal Errors)
これらは「即時停止(Abort)」または「管理者への通知」が必要な、システム基盤の問題である。
- 0x800A004C (Disk Full / ディスク容量不足)
- 原因: 書き込み先の物理的な限界。
- 戦略: 処理を中断し、クリーンアップ後に管理者にアラートを飛ばす。
2. 設計思想:レジリエンス(回復力)のあるアーキテクチャ
堅牢なツールを設計する場合、エラーハンドリングを「単一のTry-Catch的な処理」として書くのではなく、「エラーの性質に応じた戦略(Strategy Pattern)」として実装すべきである。
私が推奨する設計フローは以下の通りだ。
1. Detection(検知): `Err.Number` を捕捉する。
2. Classification(分類): エラーコードに基づき、「リトライ可能か」「スキップ可能か」「致命的か」を判定する。
3. Action(実行):
- Retry: 指数バックオフ(待ち時間を徐々に増やす)を用いた再試行。
- Quarantine(隔離): 失敗したファイルを「Error_Archive」フォルダへ移動させ、後続の正常な処理を止めない。
- Abort: プロセスを安全に終了し、リソースを解放する。
3. プロダクション・コード・テンプレート
以下に、実務レベルでそのまま利用可能な、堅牢なファイルコピー・エンジンを示す。これは単なるサンプルではない。エラーの分類、リトライロジック、そして隔離処理を統合した、プロフェッショナル向けの構造である。
' ==============================================================================
' Module: RobustFileProcessor
' Description: 高度な例外ハンドリングを備えたファイル操作エンジン
' Author: Chief Architect
' ==============================================================================
Option Explicit
' 定数定義:エラー戦略の分類
Const STRATEGY_RETRY = 1 ' 再試行
Const STRATEGY_SKIP = 2 ' スキップ(ログ記録)
Const STRATEGY_QUARANTINE = 3 ' 隔離(エラーフォルダへ移動)
Const STRATEGY_ABORT = 4 ' 即時中断
' 設定値
Const MAX_RETRIES = 3 ' 最大リトライ回数
Const RETRY_DELAY_MS = 1000 ' リトライ間隔 (ミリ秒)
Const QUARANTINE_DIR = "C:\Error_Archive\" ' 隔離用ディレクトリ
Dim fso, shell
Set fso = CreateObject("Scripting.FileSystemObject")
Set shell = CreateObject("WScript.Shell")
' --- メイン処理のシミュレーション ---
Sub Main()
Dim sourceFile, destFile
sourceFile = "C:\Data\input_data.txt"
destFile = "D:\Backup\input_data.txt"
' 隔離用ディレクトリの準備
If Not fso.FolderExists(QUARANTINE_DIR) Then
fso.CreateFolder(QUARANTINE_DIR)
End If
WScript.Echo "Processing started..."
' 堅牢なコピー実行
If SafeFileCopy(sourceFile, destFile) Then
WScript.Echo "Success: File copied successfully."
Else
WScript.Echo "Failed: File processing aborted or failed critically."
End If
' オブジェクトの明示的な解放(ライフサイクル管理)
Set fso = Nothing
Set shell = Nothing
End Sub
' ==============================================================================
' Function: SafeFileCopy
' Purpose: エラー分類に基づいた高度なファイルコピー処理
' ==============================================================================
Function SafeFileCopy(strSrc, strDest)
Dim retryCount, errStrategy, errorMsg
retryCount = 0
SafeFileCopy = False
On Error Resume Next ' エラーハンドリングを開始
Do
' コピー実行
fso.CopyFile strSrc, strDest, True
If Err.Number = 0 Then
' 【成功】
SafeFileCopy = True
Exit Do
Else
' 【エラー発生】分類フェーズ
errorMsg = Err.Description
errStrategy = ClassifyError(Err.Number)
Select Case errStrategy
Case STRATEGY_RETRY
' --- リトライ戦略 ---
retryCount = retryCount + 1
If retryCount <= MAX_RETRIES Then
WScript.Echo "Warning: Transient error (" & Err.Number & "). Retrying " & retryCount & "/" & MAX_RETRIES & "..."
WScript.Sleep RETRY_DELAY_MS
Err.Clear ' エラー状態をクリアして再試行
Else
WScript.Echo "Error: Max retries reached. Moving to Quarantine."
errStrategy = STRATEGY_QUARANTINE ' リトライ失敗後は隔離へ
End If
Case STRATEGY_SKIP
' --- スキップ戦略 ---
WScript.Echo "Error: Non-critical error (" & Err.Number & ") detected: " & errorMsg & ". Skipping file."
SafeFileCopy = True ' スキップ自体は「処理フローとしては正常」とする場合が多い
Err.Clear
Exit Do
Case STRATEGY_QUARANTINE
' --- 隔離戦略 ---
WScript.Echo "Critical File Error: " & errorMsg & ". Moving to quarantine."
PerformQuarantine strSrc
SafeFileCopy = False ' 処理自体は失敗扱い
Err.Clear
Exit Do
Case STRATEGY_ABORT
' --- 中断戦略 ---
WScript.Echo "FATAL ERROR (" & Err.Number & "): " & errorMsg & ". Aborting process."
SafeFileCopy = False
Err.Clear
Exit Do
End Select
End If
Loop While retryCount <= MAX_RETRIES
On Error GoTo 0 ' エラーハンドリングを終了
End Function
' ==============================================================================
' Function: ClassifyError
' Purpose: Err.Numberを解析し、復旧戦略を決定する
' ==============================================================================
Function ClassifyError(lNum)
Select Case lNum
' 0x800A0046: Permission Denied (ロック中など)
Case 800A0046, -2146826210
ClassifyError = STRATEGY_RETRY
' 0x800A004B: Path Not Found
' 0x800A000E: File Not Found
Case 800A004B, 800A000E, -2146826205, -2146826206
ClassifyError = STRATEGY_SKIP
' 0x800A004C: Disk Full
Case 800A004C, -2146826204
ClassifyError = STRATEGY_ABORT
Case Else
' 未知のエラーは安全のために中断
ClassifyError = STRATEGY_ABORT
End Select
End Function
' ==============================================================================
' Sub: PerformQuarantine
' Purpose: 問題のあるファイルを隔離フォルダへ移動する
' ==============================================================================
Sub PerformQuarantine(strFile)
On Error Resume Next
Dim fileName, destPath
fileName = fso.GetFileName(strFile)
destPath = QUARANTINE_DIR & fileName & "_" & Timer & ".bak"
' ファイルを隔離フォルダへ移動(失敗してもプロセスは止めない)
fso.MoveFile strFile, destPath
If Err.Number <> 0 Then
WScript.Echo "Critical: Quarantine failed! Check permissions."
End If
On Error GoTo 0
End Sub
' 実行
Main
4. アーキテクトの視点:パフォーマンスとライフサイクル
コードの堅牢性を高める一方で、パフォーマンスを犠牲にしてはならない。
オブジェクトの生存期間(Lifecycle)
上記のコードでは、`fso` オブジェクトを `Main` プロシージャの冒頭で生成し、最後に `Set fso = Nothing` で明示的に破棄している。
絶対にやってはいけないのは、ループの中で `CreateObject(“Scripting.FileSystemObject”)` を呼び出すことだ。
COMオブジェクトの生成と破棄は、OSのメモリ管理において非常に重いコストを伴う。数万個のファイルを処理する場合、この設計ミスだけで処理時間が数十分変わる。
ディスクI/Oのボトルネック
ファイル操作はCPU処理に比べ、遥かに低速なI/O待ちが発生する。
- メタデータの最小化: `fso.FileExists` や `fso.GetFolder` を過剰に呼び出さないこと。一度 `File` オブジェクトを取得したら、そのオブジェクトからプロパティを引き出す方が効率的だ。
- ネットワークパスの扱い: ネットワーク越しにFSOを叩く場合、エラー発生時のタイムアウトが非常に長いことがある。ネットワーク経由の操作を行う場合は、可能な限りローカルに一度バッファリングする設計を検討せよ。
結論
VBScriptは古くからある言語だが、その挙動は極めて決定論的だ。エラーコードを正しく解釈し、適切に分類し、設計に基づいたアクションを実行する。この「規律」こそが、プロフェッショナルな自動化エンジニアリングの根幹である。
次にエラーに遭遇したとき、あなたは `On Error Resume Next` で逃げるのか、それとも `ClassifyError` で正面から立ち向かうのか。選択はあなたに委ねられている。
