「読み取り専用」の罠を回避せよ:データ消失をゼロにする、AutoCAD VBAにおける堅牢なリカバリ・アーキテクチャ
業務自動化の現場において、エンジニアの真価が問われるのは「正常系」のコードを書いた時ではない。「予期せぬ例外」が発生した際、いかにしてデータの整合性を守り、システムを破綻させずにコントロール下に置けるか、その一点に集約される。
AutoCADの自動化ツールを開発していると、必ずと言っていいほど直面するのが「読み取り専用(Read-Only)ファイル」の壁だ。誰かがファイルを開いている、あるいはサーバーの権限設定により上書きが許可されていない。この状態で、単に `On Error Resume Next` でエラーを握りつぶすような実装をした者がいたら、私は即座にそのコードをリジェクトする。それは「自動化」ではなく、単なる「データ消失の高速化」に過ぎないからだ。
本記事では、中級者への脱皮を目指すエンジニアに向けて、読み取り専用図面を検知し、作業内容を失うことなく一時フォルダへ「別名保存」へと強制リダイレクトする、実務レベルの堅牢なエラーハンドリング設計を伝授する。
1. なぜ、あなたの「Save」は失敗するのか
多くの開発者が陥るミスは、`Document.Save` メソッドを呼び出した後にエラーが出ることを前提とした設計をしている点だ。
' --- アンチパターン:素人のコード ---
On Error Resume Next
ThisDrawing.Save
If Err.Number <> 0 Then
MsgBox "保存に失敗しました" ' これではユーザーの作業内容は消える
End If
On Error GoTo 0
この設計の致命的な欠陥は、「エラーが起きた時点では、すでにメモリ上の変更を確定させる手段を失っている可能性がある」という点にある。AutoCADのオブジェクトモデルにおいて、`ReadOnly` プロパティは「これから書き込めるか」ではなく「現在のドキュメントの属性」を示す。
プロフェッショナルな設計では、「アクションを起こす前に、オブジェクトのライフサイクルと権限を予見(Predict)する」必要がある。
2. 設計思想:リカバリ・リダイレクション・モデル
我々が構築すべきは、以下のフローを持つステートマシン的なロジックである。
1. Pre-Check (事前検証): `ReadOnly` プロパティをスキャンし、現在の保存ステータスを判定する。
2. Decision (分岐):
- 書き込み可能 $\rightarrow$ 通常の `Save`。
- 読み取り専用 $\rightarrow$ リカバリ・モードへ移行。
3. Recovery (リカバリ):
- システムの環境変数(`%TEMP%`)を利用し、衝突の起こらない一意のファイル名を生成。
- `SaveAs` を実行し、作業内容を物理的なディスクへ退避。
4. Reporting (通知): ユーザーに対し、「元のファイルは変更できなかったが、作業内容はここへ避難させた」という事実を、明確なパスと共に報告する。
3. プロダクション・コード:堅牢な保存制御モジュール
以下に、そのまま実務の共通ライブラリとして組み込めるレベルのコードを示す。ここでは、外部依存を減らしつつ、信頼性を高めるために `FileSystemObject` を活用した設計にしている。
Option Explicit
''' <summary>
''' 図面を安全に保存する。読み取り専用の場合は一時フォルダへ別名保存する。
''' </summary>
''' <param name="targetDoc">対象のAutoCADドキュメント</param>
''' <returns>保存に成功したファイルのフルパス。失敗した場合は空文字。</returns>
Public Function SafeSaveDocument(ByRef targetDoc As AcadDocument) As String
On Error GoTo ErrorHandler
Dim currentPath As String
Dim fileName As String
Dim extension As String
Dim tempFolder As String
Dim recoveryPath As String
Dim fso As Object
' FileSystemObjectの生成(Late Bindingで参照設定のトラブルを回避)
Set fso = CreateObject("Scripting.FileSystemObject")
currentPath = targetDoc.FullName
' 1. ReadOnlyプロパティの監視
If Not targetDoc.ReadOnly Then
' --- 正常系:通常の保存 ---
targetDoc.Save
SafeSaveDocument = currentPath
Exit Function
Else
' --- 異常系(リカバリ):読み取り専用の場合 ---
Debug.Print "Warning: Document is Read-Only. Redirecting to Temp folder."
' 保存先となる一時フォルダの取得 (Windows環境変数 %TEMP%)
tempFolder = Environ("TEMP") & "\"
' 元のファイル名と拡張子を分解
fileName = fso.GetBaseName(currentPath)
extension = fso.GetExtensionName(currentPath)
' 2. 重複を避けるため、タイムスタンプを付与した一意のファイル名を生成
' 例: OriginalName_20231027_153045.dwg
recoveryPath = tempFolder & fileName & "_" & Format(Now, "yyyymmdd_hhmmss") & "." & extension
' 3. 別名保存(SaveAs)の実行
' AutoCADのSaveAsは、現在のドキュメントのコンテキストを切り替えるため注意が必要
targetDoc.SaveAs recoveryPath
' 成功したパスを返す
SafeSaveDocument = recoveryPath
End If
Set fso = Nothing
Exit Function
ErrorHandler:
' エラーハンドリング:ファイルシステムエラーやAutoCAD内部エラーを捕捉
MsgBox "Critical Error during Save operation:" & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Description: " & Err.Description, vbCritical, "Save Failure"
SafeSaveDocument = ""
Set fso = Nothing
End Function
''' <summary>
''' 実装テスト用メインルーチン
''' </summary>
Public Sub Test_SafeSave()
Dim doc As AcadDocument
Set doc = ThisDrawing
Dim savedPath As String
savedPath = SafeSaveDocument(doc)
If savedPath <> "" Then
If savedPath <> doc.FullName Then
MsgBox "【重要】図面は読み取り専用だったため、以下の場所に退避しました:" & vbCrLf & savedPath, vbExclamation
Else
MsgBox "保存に成功しました: " & savedPath, vbInformation
End If
Else
MsgBox "保存プロセスが異常終了しました。データが失われた可能性があります。", vbCritical
End If
End Sub
4. アーキテクトの視点:運用上の注意点
このコードをプロダクション環境に投入する際、以下の3点を必ず考慮せよ。
① ファイルパスのトレーサビリティ(追跡可能性)
リカバリによって `%TEMP%` に保存されたファイルは、「誰が、いつ、どの元のファイルから生成したか」という情報が欠落している。
大規模な自動化システム(例えば、サーバー上の図面を巡回して処理するバッチ処理)の場合、このリカバリパスをログファイルやデータベースに書き出すロジックを必ず併設すること。さもなくば、リカバリされた図面は「迷子」になる。
② `SaveAs` によるドキュメント・コンテキストの変化
AutoCAD VBAにおいて、`Document.SaveAs` を実行すると、`ThisDrawing` が指す対象そのものが、新しく保存したファイルへと切り替わる。
ループ処理の中で複数の図面を扱っている場合、この「ドキュメントの切り替わり」を考慮していないと、次のループで意図しないファイルに対して操作を行ってしまう。「保存した後は、オブジェクトの参照が更新される」という副作用を常に意識せよ。
③ 権限の二重チェック
`ReadOnly` プロパティが `False` であっても、ネットワークドライブの瞬断や、ウイルス対策ソフトによるファイルロックによって `Save` が失敗することはある。
真に堅牢なシステムを求めるなら、`Save` 実行後に `FileExists` や `FileAttributes` を用いて、物理的にディスクに書き込まれたことを再検証するプロセスを検討すべきだ。
結論
「エラーが起きないコード」を書くのは初心者だ。「エラーが起きた後の挙動を設計する」のがプロフェッショナルである。
`ReadOnly` プロパティを単なるフラグとしてではなく、「データのライフサイクルを保護するための分岐点」として捉え直せ。その思考の転換こそが、君のツールを「ただのスクリプト」から「信頼されるシステム」へと昇華させる。
