【VBA極限設計】ファイル保存の「謎のエラー」を根絶せよ。堅牢なファイル名バリデーションの実装術
業務自動化ツールを開発していると、必ずぶち当たる壁がある。それが「ファイル保存時の予期せぬエラー」だ。
特に、ユーザーがフォーム等に入力した「タイトル」をそのままファイル名に流用する設計は、開発者にとって最大のリスク要因となる。`\` `?` “ といったWindowsの禁止文字や、環境依存文字(外字)が混入した瞬間に、あなたの渾身の自動化ツールは「パスが見つかりません」という無慈悲なエラーを吐き出し停止する。
今回は、現場のエンジニアが陥りがちな「場当たり的な置換」を卒業し、堅牢かつ保守性の高いファイル名バリデーションの実装を伝授する。
—
1. なぜ「単純なReplace」では不十分なのか
多くの初級エンジニアは、`Replace(str, “/”, “_”)` のように特定の文字を置換して終わりにする。だが、これは氷山の一角に過ぎない。
- 全角・半角の混在: 全角の `?` や `/` はスルーされる。
- 制御文字: 目に見えない制御文字がファイル名に含まれ、APIが暴走する。
- Windowsの予約語: `CON`, `PRN`, `AUX`, `NUL` 等をファイル名にすると、OSレベルで弾かれる。
- パス長制限: 260文字を超えた際のハンドリングが考慮されていない。
真のプロフェッショナルは、「ホワイトリスト方式(許可する文字以外を削ぎ落とす)」または「正規表現による一括置換」を採用する。今回は、保守性を極めた「正規表現によるサニタイズ関数」を実装する。
—
2. 実装コード:堅牢なファイル名生成エンジン
この関数を標準モジュールに埋め込んでおけば、あらゆるツールで使い回せる「最強のユーティリティ」となる。
Option Explicit
‘ —————————————————————————
‘ 概要:ファイル名として不適切な文字を安全なものに置換する
‘ 引数:inputName – ユーザー入力文字列
‘ 戻値:安全なファイル名文字列
‘ —————————————————————————
Public Function SanitizeFileName(ByVal inputName As String) As String
Dim regEx As Object
Set regEx = CreateObject(“VBScript.RegExp”)
‘ 1. Windows禁止文字・制御文字を正規表現でマッチング
‘ [\\/:?”<>|] はWindowsの禁止文字
‘ [\x00-\x1F] は制御文字
regEx.Pattern = “[\\/:?””<>|\x00-\x1F]”
regEx.Global = True
‘ 2. 禁止文字をアンダースコアに置換
Dim safeName As String
safeName = regEx.Replace(inputName, “_”)
‘ 3. 長すぎる場合は切り詰める(パス長エラー回避:Windowsは260文字制限)
‘ フォルダパスを含めた長さを考慮し、念のため50文字程度に制限するのが吉
If Len(safeName) > 50 Then
safeName = Left(safeName, 50)
End If
‘ 4. Windows予約語の回避(簡易チェック)
Select Case UCase(safeName)
Case “CON”, “PRN”, “AUX”, “NUL”, “COM1”, “LPT1”
safeName = safeName & “_File”
End Select
SanitizeFileName = Trim(safeName)
End Function
—
3. 実践:プレゼンテーション保存への統合
この関数を使い、プレゼンテーションを安全に保存するプロシージャがこちらだ。
Sub SavePresentationSafely(pptObj As Presentation, folderPath As String)
Dim rawTitle As String
Dim safeName As String
Dim fullPath As String
‘ タイトルを取得(未設定なら現在時刻を付与)
rawTitle = IIf(pptObj.BuiltinDocumentProperties(“Title”) <> “”, _
pptObj.BuiltinDocumentProperties(“Title”), _
“Presentation_” & Format(Now, “yyyyMMdd_HHmmss”))
‘ サニタイズ実行
safeName = SanitizeFileName(rawTitle)
‘ パス結合(末尾のセパレータを確認)
If Right(folderPath, 1) <> “\” Then folderPath = folderPath & “\”
fullPath = folderPath & safeName & “.pptx”
‘ 保存実行(エラーハンドリングを忘れずに)
On Error Resume Next
pptObj.SaveAs Filename:=fullPath
If Err.Number <> 0 Then
MsgBox “保存に失敗しました: ” & Err.Description, vbCritical
Else
Debug.Print “保存成功: ” & fullPath
End If
On Error GoTo 0
End Sub
—
4. チーフアーキテクトからの助言
パフォーマンスと設計の要諦
1. 正規表現オブジェクトの再利用: ループ内で何度も `CreateObject` を呼ぶのは非効率の極みだ。頻繁に呼び出す場合は、モジュールレベル変数として保持し、インスタンスを使い回す設計にすること。
2. ログの重要性: エラー発生時に「なぜ失敗したか」を追えるよう、`Err.Description` をログ出力する仕組みは必ず入れる。ユーザーは「なぜ動かないか」を教えてくれない。
3. 環境依存文字への対応: 上記の正規表現はASCII/標準Windows文字をカバーしている。もし特殊な絵文字などが含まれる可能性がある場合、`ADODB.Stream` を使用してUTF-8変換を噛ませる等の高度な処理が必要になるが、まずは上記のバリデーションで99%のエラーは防げる。
結論:
自動化エンジニアの価値は、プログラムを動かすことではなく、「動かない原因を徹底的に排除すること」にある。このロジックを実装することで、あなたのツールは「エラーで止まるおもちゃ」から「信頼できる業務システム」へと昇華するはずだ。
次は、これを応用して「一括PDF変換後のファイル名管理」を実装してみるといい。道は開ける。
