【実務・中級編】【初心者向け】CreateTextFileのOverwrite引数をFalseに指定した際のエラーハンドリングと上書き事故防止 – VBScript (Visual Basic Scripting Edition)解析バイブル

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

データの破壊を防げ:FSO `CreateTextFile` における「Err 58」の制御と、プロフェッショナルな堅牢設計

業務自動化の世界において、我々が扱うのは単なる「文字列」ではない。それは企業の意思決定を支える「資産」であり、一歩間違えれば数年分の実績データを一瞬で灰にする「凶器」にもなり得る。

特に、VBScriptを用いたファイル操作において、`FileSystemObject` (FSO) の `CreateTextFile` メソッドを扱う際、多くのジュニアエンジニアが「既存ファイルの上書き」という、極めて初歩的かつ致命的な設計ミスを犯す。

本稿では、`Overwrite` 引数を `False` に設定した際に発生するエラー `Err.Number = 58`(ファイルは既に存在します)を単なる「エラー」としてではなく、「データの整合性を守るための防波堤」としてどう扱うべきか。その極限の知見を伝授する。

1. なぜ「If FileExists」による事前チェックでは不十分なのか

初心者は、エラーを回避するために以下のようなコードを書きたがる。

' --- 避けるべき、脆弱な実装例 ---
If Not fso.FileExists(strPath) Then
    Set ts = fso.CreateTextFile(strPath, False)
    ' ... 書き込み処理 ...
Else
    MsgBox "ファイルが既にあります。"
End If

一見、論理的に正しいように見える。しかし、これは「TOCTOU (Time-of-Check to Time-of-Use)」と呼ばれる、マルチタスク・共有環境における典型的な脆弱性だ。

「ファイルが存在しないことを確認した瞬間」から「ファイルを作成する瞬間」までの、コンマ数秒の間に、別のプロセスやユーザーが同じ名前のファイルを作成する可能性がある。このわずかな隙が、システムを不安定にし、予期せぬ挙動を引き起こす。

プロフェッショナルは、「確認してから動く」のではなく、「動いてみて、結果を制御する」という、より堅牢なアプローチを取る。

2. エラーハンドリングの設計思想:Err 58 をどう飼い慣らすか

`CreateTextFile(filename, False)` を実行した際、ファイルが既に存在していれば、VBScriptは `Err.Number = 58` を吐いて停止する。

我々の任務は、このエラーを「無視」することではない。このエラーを「状態の検知」として利用し、ビジネスロジックに基づいた適切な分岐(リネームして保存するのか、処理を中断するのか、あるいはログに記録するのか)を行うことだ。

設計の3原則

1. 局所化 (Localization): `On Error Resume Next` をスクリプト全体に適用してはならない。エラーハンドリングは、操作が予測不能な「境界線」でのみ、最小限のスコープで行う。
2. 意図の明確化: エラーを握りつぶしてはいけない。エラーが発生したという事実は、必ずログまたは呼び出し元に伝播させる。
3. 冪等性 (Idempotency) の確保: 同じスクリプトを何度実行しても、データの破壊が起きず、かつ一貫した結果が得られるように設計する。

3. 【実践】プロダクション・レディな実装パターン

以下に、実務でそのまま利用可能な、堅牢なファイル作成関数を示す。このコードは、単にエラーを防ぐだけでなく、エラー発生時に「タイムスタンプを付与して別名で保存する」という、業務継続性を考慮した機能を備えている。

' ==========================================================================
' Module: FileSystemManager
' Description: 堅牢なファイル操作を提供するプロフェッショナル用関数
' ==========================================================================

Option Explicit

' メイン処理のデモ
Call Main()

Sub Main()
    Dim targetPath
    targetPath = "C:\Temp\Report.txt" ' テスト用のパス
    
    ' フォルダが存在しない場合は作成しておく(前提条件の整備)
    Dim fso: Set fso = CreateObject("Scripting.FileSystemObject")
    Dim folderPath: folderPath = fso.GetParentFolderName(targetPath)
    If Not fso.FolderExists(folderPath) Then fso.CreateFolder(folderPath)

    ' 堅牢なファイル作成の呼び出し
    Dim resultPath
    resultPath = SafeCreateTextFile(targetPath, "業務報告データ: " & Now())

    If resultPath <> "" Then
        MsgBox "成功: " & resultPath, vbInformation, "完了"
    Else
        MsgBox "失敗: ファイルの作成に失敗しました。", vbCritical, "エラー"
    End If
End Sub

''' <summary>
''' ファイルの重複を回避しつつ、安全にテキストファイルを作成する。
''' </summary>
''' <param name="filePath">作成するファイルのフルパス</param>
''' <param name="content">書き込む内容</param>
''' <returns>作成されたファイルのパス。失敗した場合は空文字を返す。</returns>
Function SafeCreateTextFile(filePath, content)
    Dim fso, ts, errNum, finalPath
    Set fso = CreateObject("Scripting.FileSystemObject")
    finalPath = filePath
    
    ' --- エラーハンドリングの境界線 ---
    On Error Resume Next
    
    ' Overwrite = False に設定し、既存ファイルの破壊を物理的に防ぐ
    Set ts = fso.CreateTextFile(finalPath, False)
    
    ' エラー判定
    errNum = Err.Number
    
    If errNum = 0 Then
        ' 【正常系】ファイル作成成功
        ts.WriteLine content
        ts.Close
        Set ts = Nothing
    ElseIf errNum = 58 Then
        ' 【特定エラー系】ファイルが既に存在する場合
        ' 既存ファイルを破壊せず、タイムスタンプを付与して別名でリトライする設計
        Err.Clear
        Dim baseName, ext, timestamp
        baseName = fso.GetBaseName(filePath)
        ext = fso.GetExtensionName(filePath)
        timestamp = FormatDateTime(Now, 2) ' YYYY/MM/DD形式(環境により調整)
        ' 記号を置換してファイル名として使用可能にする
        timestamp = Replace(Replace(timestamp, "/", ""), "-", "")
        
        finalPath = fso.GetParentFolderName(filePath) & "\" & baseName & "_" & timestamp & "." & ext
        
        ' 再試行(再帰呼び出しではなく、一度きりのリトライとして実装)
        Set ts = fso.CreateTextFile(finalPath, False)
        If Err.Number = 0 Then
            ts.WriteLine content
            ts.Close
            Set ts = Nothing
        Else
            ' リトライも失敗した場合(権限不足など)
            finalPath = ""
        End If
    Else
        ' 【未知のエラー系】権限不足やディスクフルなど、別の致命的な問題
        ' ここでは詳細なエラー内容をログに投げるべき(今回は簡易的に)
        ' WScript.Echo "Critical Error: " & Err.Description 
        finalPath = ""
    End If
    
    On Error GoTo 0 ' エラーハンドリングを即座に解除
    ' ----------------------------------

    SafeCreateTextFile = finalPath
End Function

4. アーキテクトの視点:ファイル操作からデータベース連携へ

この「存在チェックとエラーハンドリング」の考え方は、ファイルシステムに留まらない。SQLを用いたデータベース操作においても、全く同じ原理が適用される。

例えば、SQL Server等へのデータ投入において、`INSERT` 文を無条件に発行するのは、`CreateTextFile(path, True)` を実行するのと同じくらい危険だ。

  • ファイル操作における `Overwrite = False`

$\rightarrow$ DB操作における `WHERE NOT EXISTS` または `UPSERT (MERGE)`

既存のレコードを破壊(上書き)してしまうのか、それとも新しいバージョンとして追加(履歴管理)するのか。この設計判断こそが、システムエンジニアと、単なるスクリプトライターを分かつ境界線である。

結論

`Err.Number = 58` は、あなたのコードが間違っていることを告げる宣告ではない。それは、「既存のデータに干渉しようとしているが、本当にそれで良いのか?」とシステムが問いかけている、守護的なシグナルである。

エラーを隠蔽するのではなく、エラーをロジックの一部として組み込む。この「防御的プログラミング」の姿勢こそが、業務の自動化を「便利なツール」から「信頼に足るインフラ」へと昇華させるのだ。

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