【実務・中級編】【上級プロフェッショナル向け】FSO操作中に発生する各種I/Oエラー(0x800A0046, 0x800A004C等)をErr.Numberで分類し高度な障害復旧ロジックを組む技法 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク
スポンサーリンク

【極限の堅牢性】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` で正面から立ち向かうのか。選択はあなたに委ねられている。

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