【極限の堅牢性】SolidWorks図面自動生成における「ステートマシン型リトライ機構」と「例外安全設計」の全貌
自動化エンジニアとして、多くの者が陥る致命的な罠がある。それは、「正常系(Happy Path)のコードしか書かない」ことだ。
「アセンブリを開き、図面を作成し、ビューを配置して保存する」――。
リファレンス通りに書かれたそのコードは、あなたのローカル環境では完璧に動作するだろう。しかし、数千枚の図面を生成する大規模プロジェクト、あるいは不安定なネットワーク越しにPDM(Product Data Management)へアクセスする実務環境に放り込まれた瞬間、そのコードは「ゴミ」へと成り下がる。
ネットワークの瞬断、COMインターフェースの応答遅延、あるいは巨大なアセンブリがメモリを食いつぶした際に発生する予期せぬ例外。これらが一つでも発生すれば、プロセスは停止し、生成途中のファイルは破損し、ユーザーは二度とあなたのツールを使いたがらなくなる。
本記事では、単なる「マクロ」を、プロフェッショナルな「プロダクション・ソフトウェア」へと昇華させるための、ステートマシン(状態遷移)に基づいたリトライ機構と例外安全設計について、その設計思想と実装パターンを徹底解説する。
1. なぜ、標準的な `On Error GoTo` では足りないのか
一般的なVBA開発者は、エラーが発生したら `Err.Number` を見て、メッセージボックスを出して終了する。これは「エラー処理」であって、「エラー制御」ではない。
図面生成プロセスにおける真の課題は、「エラーが起きた瞬間に、プロセスのどの段階(State)にいたのかを正確に把握し、そこから安全に復旧(Recovery)できるか」にある。
致命的な失敗パターン
1. 不完全な保存: `SaveAs` 中にネットワークが切断され、ファイルが0KBで残る。
2. リソースのリーク: エラーで中断したことにより、SolidWorksのメモリ上に開いたままの図面インスタンスが残り続け、次回の実行を阻害する。
3. 二重実行の罠: リトライした際に、前回の失敗したプロセスの残骸が干渉し、ビューの重複配置などが起こる。
これらを回避するためには、プロセスを「単一の処理」として捉えるのではなく、「状態(State)を持つトランザクション」として設計する必要がある。
2. 設計思想:ステートマシンと指数バックオフ
堅牢なシステムを構築するために、以下の2つの概念を導入する。
① ステートマシン(State Machine)による状態管理
図面生成プロセスを以下の状態に定義する。
- `STATE_INIT`: 初期化(環境確認、ディレクトリ準備)
- `STATE_OPEN_ASSEMBLY`: アセンブリ読み込み
- `STATE_CREATE_DRAWING`: 図面テンプレート展開
- `STATE_PLACE_VIEWS`: ビュー配置(ここが最も重く、エラーが起きやすい)
- `STATE_SAVE_AND_CLOSE`: 保存およびクローズ
各ステップの完了を「状態の遷移」として記録することで、エラー発生時に「どの状態からリトライすべきか」をプログラムが自律的に判断できる。
② 指数バックオフ(Exponential Backoff)によるリトライ
ネットワークやCOMのタイムアウトは、発生直後に即座にリトライしても解決しないことが多い。むしろ、リトライの頻度を `1秒 → 2秒 → 4秒 → 8秒…` と指数関数的に増やしていくことで、システムの回復を待ちつつ、リソースへの負荷を抑える。
3. プロダクション・コード実装例
以下に、クラスモジュールを活用した堅牢な制御フレームワークの雛形を示す。VBAにおける「非同期」は、単一スレッドの制約上、`DoEvents` を適切に挟みつつ、状態を保持したループ制御を行うことで実現する。
Class Module: `clsDrawingJob`
このクラスが、ジョブのライフサイクルとリトライロジックを統括する。
' ======================================================================================
' Class Name: clsDrawingJob
' Description: 図面生成プロセスのステート管理と堅牢なリトライ制御を行う
' ======================================================================================
Option Explicit
Public Enum JobState
STATE_IDLE = 0
STATE_INIT = 1
STATE_OPEN_ASSEMBLY = 2
STATE_CREATE_DRAWING = 3
STATE_PLACE_VIEWS = 4
STATE_SAVE_AND_CLOSE = 5
STATE_COMPLETED = 6
STATE_FAILED = 7
End Enum
Private m_swApp As SldWorks.SldWorks
Private m_CurrentState As JobState
Private m_MaxRetries As Integer
Private m_RetryCount As Integer
' プロパティ
Public Property Get CurrentState() As JobState: CurrentState = m_CurrentState: End Property
' 初期化
Private Sub Class_Initialize()
Set m_swApp = Application.SldWorks
m_CurrentState = STATE_IDLE
m_MaxRetries = 3 ' 最大リトライ回数
m_RetryCount = 0
End Sub
' メイン実行エンジン
Public Function ExecuteJob(ByVal assemblyPath As String, ByVal drawingTemplate As String) As Boolean
On Error GoTo ErrorHandler
m_CurrentState = STATE_INIT
' --- ステートマシン・ループ ---
Do While m_CurrentState <> STATE_COMPLETED And m_CurrentState <> STATE_FAILED
Select Case m_CurrentState
Case STATE_INIT
Debug.Print "Step: Initializing..."
' ディレクトリ存在確認などのロジック
m_CurrentState = STATE_OPEN_ASSEMBLY
Case STATE_OPEN_ASSEMBLY
Debug.Print "Step: Opening Assembly..."
' アセンブリを開く処理 (ここにCOM例外が発生しやすい)
' SimulateSuccess:
m_CurrentState = STATE_CREATE_DRAWING
Case STATE_CREATE_DRAWING
Debug.Print "Step: Creating Drawing..."
' 図面作成処理
m_CurrentState = STATE_PLACE_VIEWS
Case STATE_PLACE_VIEWS
Debug.Print "Step: Placing Views..."
' ビュー配置処理 (重い処理)
m_CurrentState = STATE_SAVE_AND_CLOSE
Case STATE_SAVE_AND_CLOSE
Debug.Print "Step: Saving..."
' 保存処理 (ネットワーク切断リスク)
m_CurrentState = STATE_COMPLETED
End Select
' 各ステップ完了ごとにリトライ回数をリセット(新しいフェーズに入ったため)
m_RetryCount = 0
DoEvents ' OSに制御を戻し、フリーズを防ぐ
Loop
ExecuteJob = (m_CurrentState = STATE_COMPLETED)
Exit Function
ErrorHandler:
m_RetryCount = m_RetryCount + 1
Debug.Print "!!! Error Detected: " & Err.Description & " (Retry: " & m_RetryCount & ")"
If m_RetryCount <= m_MaxRetries Then
' 指数バックオフによる待機 (2^n 秒)
Dim waitTime As Double
waitTime = 2 ^ m_RetryCount
Debug.Print "Waiting for " & waitTime & " seconds before retry..."
' VBAで待機を実現(精度は低いが実務上十分)
Dim startWait As Double: startWait = Timer
Do While Timer < startWait + waitTime
DoEvents
Loop
' エラー発生時のクリーンアップ(重要!)
Call SafeCleanup
' 同じ状態から再開
Resume ' エラー発生箇所へ戻るのではなく、ErrorHandlerからの制御フローを維持
Else
Debug.Print "Critical Error: Max retries exceeded."
m_CurrentState = STATE_FAILED
ExecuteJob = False
End If
End Function
' 例外発生時の安全なリソース解放
Private Sub SafeCleanup()
On Error Resume Next
Debug.Print "Executing SafeCleanup..."
' ここに、開いているドキュメントを閉じる、
' 掴んでいるファイルハンドルを解放する、等の処理を記述する
' 例: m_swApp.CloseDoc m_ActiveDocName
Err.Clear
End Sub
標準モジュールからの呼び出し例
Sub Main_ProductionGrade()
Dim job As New clsDrawingJob
Dim success As Boolean
Dim assyPath As String: assyPath = "C:\Data\LargeAssembly.sldasm"
Dim template As String: template = "C:\Templates\Standard.drwdot"
Debug.Print "=== Starting Robust Drawing Generation ==="
success = job.ExecuteJob assyPath, template
If success Then
MsgBox "Process completed successfully!", vbInformation
Else
MsgBox "Process failed after multiple retries. Check logs.", vbCritical
End If
Debug.Print "=== Process Finished ==="
End Sub
4. プロフェッショナルのための追加アドバイス
ネットワーク共有(UNCパス)の扱い
図面を保存する際、`SaveAs` にUNCパス(`\\Server\Folder\…`)を直接渡すと、ネットワークの一瞬の瞬断でCOM例外を吐いて停止します。
対策: 一度ローカルの `TEMP` ディレクトリに保存し、成功を確認した後に `FileSystemObject` を用いてサーバーへ移動(Move)させる「二段構え」の設計を強く推奨する。
イベントフックによるバックアップ(高度な実装)
もし、より高度な「非同期バックアップ」を実装したいのであれば、SolidWorksの `SldWorksEvents` を使用して、ドキュメントの変更(`DocumentSave`)を監視し、保存が完了した瞬間にバックグラウンドで別のプロセス(またはExcel等の別インスタンス)へファイルをコピーする仕組みを構築すべきである。
ログの重要性
`Debug.Print` は開発用だ。プロダクション環境では、必ず 「タイムスタンプ付きのテキストログ」 を出力せよ。
「いつ、どのステートで、どのエラーコード(`Err.Number`)が発生し、何回目のリトライで成功(あるいは失敗)したか」が記録されていなければ、現場でトラブルが起きた際にデバッグすることは不可能だ。
まとめ
堅牢な自動化ツールとは、単に「速い」ツールではない。「失敗したときに、いかに美しく、かつ最小限の被害で立ち直れるか」を設計できたツールである。
今回紹介したステートマシンとリトライ機構を、あなたの次なるプロジェクトに組み込んでほしい。コードの書き方が「スクリプト」から「システム」へと変わる瞬間、あなたは真のエンジニアへと進化するはずだ。
