【実務・中級編】【実務中級者向け】FSOによるファイル更新時のデータ破損を防ぐ「アトミック書き換え(一時作成+瞬時置換)」パターン – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

FSOによるファイル更新時のデータ破損を防ぐ「アトミック書き換え」パターン

こんにちは。チーフアーキテクトの私だ。
日々の業務自動化において、FileSystemObject (FSO) を使ったテキストや設定ファイルの読み書きは、いわば「空気のような存在」だ。おそらく君たちも、`OpenTextFile` でファイルを開き、データを書き込んで閉じる、という処理を毎日のように書いていることだろう。

だが、問いたい。
「そのファイルの書き込み中に、万が一の停電、OSの強制シャットダウン、あるいはスクリプトの予期せぬランタイムエラーが発生したら、ファイルはどうなるか?」

答えは残酷だ。データは破損し、0バイトの空ファイルになるか、半端な書きかけのゴミデータが残され、次の起動時にシステムは沈黙する。
本稿では、レガシーと言われがちなVBScriptであっても、モダンなシステム開発に匹敵する「堅牢性(ロバストネス)」を担保するための極意、アトミック書き換え(Atomic Replace)パターンを伝授する。

—

なぜ従来の「直接上書き」はプロとして失格なのか?

多くのプログラマ(自称含む)が書くコードはこうだ。

‘ 【アンチパターン】直接上書きの例
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set file = fso.OpenTextFile(“C:\App\config.json”, 2, True) ‘ 2 = ForWriting
file.WriteLine(“{ “”status””: “”active”” }”)
file.Close

一見、何の問題もないように見える。しかし、FSOの `ForWriting` モードでファイルを開いた瞬間、既存のファイルはクリアされ(あるいはサイズが0になり)、書き込みが完了するまでの間、ファイルは「不完全な状態」でストレージに存在し続ける。

この瞬間にプロセスがクラッシュしたら?
ビジネスの現場において、ファイル破損による「原因不明のフリーズ」や「設定消失」は、顧客の信頼を失う致命傷になり得る。

我々が目指すべきは、ファイル更新を「一瞬で完了する不可分な(Atomicな)操作」に見せかけることだ。

—

究極の解決策:アトミック書き換え(一時作成 + 瞬時置換)

データベーストランザクションの世界では常識だが、ファイルシステムにおいても「本番ファイルを直接いじらない」のが鉄則だ。

アトミック書き換えのアルゴリズムはこうだ:

1. 一時ファイル(Temp File)の生成:本番ファイルと同じフォルダ、あるいは安全な場所に、ランダムな別名で一時ファイルを作成する。
2. 安全な書き込み:その一時ファイルに対してデータをすべて書き込み、確実にクローズ(フラッシュ)する。
3. 瞬時置換(Move/Replace):書き込みが100%成功したことを確認した後、FSOの機能(またはOSのファイルシステム機能)を使って、一時ファイルを本番ファイルに一撃で上書きリネームする。

ファイルシステムにおいて、同一ボリューム内での「リネーム(Move)」はOSレベルでアトミック(不可分)に処理される。つまり、ユーザーから見ると「古いファイル」から「新しいファイル」へ、一瞬で切り替わるのだ。途中の状態が外部に露出することはない。

—

実装コード:プロダクション・レディなVBScript

それでは、実際の現場でそのまま使える、エラーハンドリングを網羅したプロダクションコードを提示しよう。

Option Explicit

‘ ==============================================================================
‘ プロシージャ名: SafeWriteTextFile
‘ 概要: FSOを用いたアトミック書き換えによる安全なファイル出力
‘ 引数:
‘ ByVal strFilePath : 出力先の本番ファイルパス
‘ ByVal strContent : 書き込む文字列
‘ 戻り値: Boolean (成功:True, 失敗:False)
‘ ==============================================================================
Function SafeWriteTextFile(ByVal strFilePath, ByVal strContent)
Dim fso, objFolder, strFolder, strTempFileName, strTempFilePath
Dim objFile, errNum

SafeWriteTextFile = False
Set fso = CreateObject(“Scripting.FileSystemObject”)

On Error Resume Next

‘ 1. 対象ファイルのフォルダパスを取得
strFolder = fso.GetParentFolderName(strFilePath)
If strFolder = “” Then strFolder = “.”

‘ フォルダが存在しない場合は異常終了
If Not fso.FolderExists(strFolder) Then
WScript.Echo “Error: 宛先フォルダが存在しません -> ” & strFolder
Exit Function
End If

‘ 2. 同一フォルダ内にユニークな一時ファイル名を生成 (衝突防止)
‘ プレフィックスに “~tmp” を付与し、GUID的なランダム性を利用
Randomize
strTempFileName = “~tmp_” & Year(Now) & Month(Now) & Day(Now) & _
Hour(Now) & Minute(Now) & Second(Now) & “_” & _
Int((9999 – 1000 + 1) Rnd + 1000) & “.tmp”
strTempFilePath = fso.BuildPath(strFolder, strTempFileName)

‘ 3. 一時ファイルへ書き込み
‘ ForWriting (2), Create (True), Unicode (TristateTrue = -1 または ASCII = 0)
‘ ここでは汎用性を考慮してASCII(0)でオープン
Set objFile = fso.OpenTextFile(strTempFilePath, 2, True, 0)
If Err.Number <> 0 Then
WScript.Echo “Error: 一時ファイルの作成に失敗しました. 理由: ” & Err.Description
Exit Function
End If

objFile.Write strContent
objFile.Close

If Err.Number <> 0 Then
WScript.Echo “Error: 一時ファイルへの書き込み中にエラーが発生しました. 理由: ” & Err.Description
Call CleanupTempFile(fso, strTempFilePath)
Exit Function
End If

‘ 4. アトミック置換(Moveメソッドによる上書き)
‘ ※同一ボリューム内であれば、Moveはアトミックに実行される
fso.CopyFile strTempFilePath, strFilePath, True ‘ 上書き許可(True)

If Err.Number <> 0 Then
WScript.Echo “Error: 本番ファイルの置換に失敗しました. 理由: ” & Err.Description
Call CleanupTempFile(fso, strTempFilePath)
Exit Function
End If

‘ 5. 正常終了:不要になった一時ファイルを削除
Call CleanupTempFile(fso, strTempFilePath)

On Error GoTo 0
Set fso = Nothing
SafeWriteTextFile = True
End Function

‘ ——————————————————————————
‘ 内部ヘルパー: 一時ファイルの確実なクリーンアップ
‘ ——————————————————————————
Sub CleanupTempFile(ByVal fso, ByVal strTempFilePath)
On Error Resume Next
If fso.FileExists(strTempFilePath) Then
fso.DeleteFile strTempFilePath, True
End If
On Error GoTo 0
End Sub

‘ ==============================================================================
‘ 実行テスト用サンプル
‘ ==============================================================================
Dim targetPath, data
targetPath = “C:\App\settings.dat”
data = “Server=192.168.1.100” & vbCrLf & “Timeout=30” & vbCrLf & “Updated=” & Now

If SafeWriteTextFile(targetPath, data) Then
WScript.Echo “ファイルの安全な書き換えに成功しました。”
Else
WScript.Echo “書き換え処理が異常終了しました。”
End If

—

匠の視点:ファイルシステム連携における注意点

このコードを運用するにあたり、チーフアーキテクトとしていくつか実務上の重要知見を添えておく。

1. 同一ボリューム(同一ドライブ)であること

FSOの `CopyFile` や `MoveFile` を用いてアトミックな置換を行う場合、「一時ファイルと本番ファイルが同一の物理ドライブ(ボリューム)」に存在していなければならない。
もし異なるドライブ(例: `C:` から `D:`)に一時ファイルを作って移動させようとすると、OS内部で「データのコピー + 旧ファイルの削除」という非アトミックな2段階処理にフォールバックしてしまう。これでは途中で電源が落ちた際にゴミファイルが残る。
必ず「書き込み先ファイルと同一のフォルダ内」に一時ファイルを生成すること。

2. ファイルロック(排他制御)の限界

VBScriptのFSOは、非常にシンプルである反面、高度なロック制御(他プロセスが掴んでいるファイルを無理やり奪うなど)はできない。
他のプロセス(常駐監視ツールなど)が本番ファイルをガッチリ掴んでいる場合、最後の `CopyFile`(上書き)の瞬間でエラー(Permission Denied / 権限がありません)になる。
しかし、それこそが「中途半端な書き込みを防げた」という安全性の証明なのだ。エラーをキャッチし、ログを残してリトライする設計を必ず組み込んでほしい。

3. 一時ファイルのゴミ掃除(ゾンビファイル対策)

万が一、スクリプトがクラッシュし、例外処理もすり抜けて一時ファイル (`~tmp_.tmp`) が残ってしまった場合を想定せよ。
定期バッチや、次回のスクリプト起動時に「古い一時ファイル(例: 24時間以上前の `~tmp_` ファイル)」を自動掃海するガーベッジコレクションのロジックを仕込んでおくと、よりエンタープライズな仕上がりになる。

—

結びにかえて

「動けばいいや」で書かれたコードと、こうしたインフラストラクチャの物理的制約まで考慮して設計されたコードの差は、運用フェーズに入った瞬間に残酷なまでの差となって現れる。

VBScriptは古い言語かもしれないが、そこで動かすシステムをプロフェッショナルたらしめるのは、いつの時代もエンジニアの「こだわり」だ。
君たちの業務自動化スクリプトにこのアトミック書き換えパターンを導入し、予期せぬトラブルからデータを守り抜いてほしい。

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