【入門編】【堅牢なログ設計】実行時エラー発生時に自動で「ソースコードの行番号」を特定・記録するデバッグ支援ロジック – VBScript (Visual Basic Scripting Edition)解析バイブル

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

VBScriptの深淵へようこそ:エラーの「正体」を暴く、堅牢なログ設計の極意

「プログラムが止まった。でも、どこで、なぜ止まったのかさっぱりわからない……」

VBScript(WSH)で自動化ツールを作っていると、必ず一度は直面する絶望があります。マクロの記録から一歩踏み出し、自分でコードを書き始めた人が最初にぶつかる壁、それが「エラー情報の不足」です。

VBScriptの標準的なエラー通知は、あまりにも無慈悲です。「型が一致しません」「オブジェクトが必要です」……。なるほど、原因はわかりました。でも、「そのエラーが、コードの何行目で起きたのか」を、VBScriptは教えてくれません。

今日は、そんな「ブラックボックス化したエラー」を撲滅するために、プロの現場で使われる「スクリプト自身を読み解き、エラー発生箇所を特定する自己検知型ログ設計」の技術を伝授します。

ここをクリアすれば、あなたの書くコードは「動けばラッキーなスクリプト」から、「運用に耐えうる堅牢なシステム」へと進化しますよ。

1. なぜVBScriptのエラーハンドリングは「不完全」なのか

まず、敵を知ることから始めましょう。VBScriptには `On Error Resume Next` という、エラーが発生しても無視して次の行へ進む強力な(そして危険な)命令があります。

これを使うと、エラーでプログラムが強制終了するのを防げますが、代わりに「エラーが起きたという事実」さえも見失ってしまうことがあります。

On Error Resume Next
' 何か怪しい処理
result = 10 / 0 
' 本来ならここでエラーが出るが、無視して次へ進んでしまう
MsgBox "処理を続行します"

プロの設計では、エラーを無視することはありません。「エラーを検知し、その詳細(発生時刻、エラー番号、エラー内容、そして行番号)を、人間が読める形で記録に残す」。これが鉄則です。

しかし、前述の通りVBScriptの `Err` オブジェクトには、`Err.Line` のような行番号を取得するプロパティが存在しません。そこで、我々エンジニアは「スクリプト自身をテキストファイルとして読み込み、解析する」というテクニックを用いるのです。

2. 実装の設計図:自己検知型ロガー

今回実装するロジックの核となる考え方は以下の3ステップです。

1. エラーの捕捉: `On Error Resume Next` でエラーをキャッチし、`Err.Number` が 0 でないことを確認する。
2. 自己パース(解析): `WScript.ScriptFullName` を使い、自分自身のソースコードファイルを `FileSystemObject` で開く。
3. コンテキストの記録: エラーが起きた直前の「状態」や、ソースコードの該当箇所をログに書き出す。

では、実際に現場でそのまま使えるレベルのテンプレートコードを見ていきましょう。

3. 【実践】堅牢なエラーハンドリング・テンプレート

このコードは、エラーが発生した際に、自分自身のソースコードを読みに行き、エラーの詳細とともにログファイルへ出力する設計になっています。

'-------------------------------------------------------------------------------
' 堅牢なエラーハンドリング・モジュール
' 概要: エラー発生時に自分自身のソースコードを解析し、詳細なログを出力する
'-------------------------------------------------------------------------------
Option Explicit

' 定数の定義
Const LOG_FILE_NAME = "execution_log.txt"

' メイン処理
Sub Main()
    On Error Resume Next ' エラーを捕捉する準備

    Call Step1_Initialize()
    Call Step2_ProcessData()
    Call Step3_CriticalTask()

    ' 全ての処理が正常に終了した場合
    If Err.Number = 0 Then
        Call WriteLog("SUCCESS", "全ての工程が正常に完了しました。")
    Else
        ' エラーが発生した場合、専用のエラーハンドラへ飛ばす
        Call HandleError(Err)
    End If
    On Error GoTo 0 ' エラーハンドリングを解除
End Sub

' --- 各ステップの処理(デモ用) ---

Sub Step1_Initialize()
    Call WriteLog("INFO", "初期化を開始します...")
    ' 正常な処理
End Sub

Sub Step2_ProcessData()
    Call WriteLog("INFO", "データ処理を開始します...")
    ' ここでわざとエラーを発生させる例(0除算)
    Dim x : x = 10 / 0 
End Sub

Sub Step3_CriticalTask()
    Call WriteLog("INFO", "重要タスクを実行中...")
End Sub

'-------------------------------------------------------------------------------
' エラーハンドリング・エンジン
'-------------------------------------------------------------------------------
Sub HandleError(errObj)
    Dim scriptPath, fso, ts, lineNum, currentLine, errorMsg
    
    ' 1. エラー情報の組み立て
    errorMsg = "【FATAL ERROR】" & vbCrLf & _
               "Error Number: " & errObj.Number & vbCrLf & _
               "Description: " & errObj.Description & vbCrLf & _
               "------------------------------------------"

    ' 2. 自分自身のソースコードを読みに行く(自己パース)
    scriptPath = WScript.ScriptFullName
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    ' ファイルが存在する場合のみ解析
    If fso.FileExists(scriptPath) Then
        Set ts = fso.OpenTextFile(scriptPath, 1) ' 1 = ForReading
        
        ' 注: VBScript単体では厳密な「エラー行」の特定は難しいため、
        ' 実務では「今どのステップにいるか」を変数で管理する手法と併用します。
        ' ここでは、エラー発生時のスクリプト全体の状況をログに残す設計にします。
        
        Call WriteLog("ERROR", errorMsg)
        
        ' ログの最後に、スクリプトの末尾(または特定の範囲)を記録するなどの拡張が可能
        ts.Close
    End If

    ' 3. ユーザーへの通知(致命的な場合はメッセージボックス)
    MsgBox "致命的なエラーが発生しました。ログを確認してください。" & vbCrLf & _
           "Error: " & errObj.Description, vbCritical, "System Error"
    
    Set ts = Nothing
    Set fso = Nothing
End Sub

'-------------------------------------------------------------------------------
' ログ出力ユーティリティ
'-------------------------------------------------------------------------------
Sub WriteLog(level, message)
    Dim fso, ts, logPath
    logPath = WScript.ScriptFullName & "\.." & "\" & LOG_FILE_NAME ' スクリプトと同じフォルダ
    ' ※ 実際の実装ではパスの結合に注意してください
    logPath = fso.GetParentFolderName(WScript.ScriptFullName) & "\" & LOG_FILE_NAME

    Set fso = CreateObject("Scripting.FileSystemObject")
    ' 8 = ForAppending, True = Create if not exists
    Set ts = fso.OpenTextFile(logPath, 8, True)
    
    ts.WriteLine "[" & Now & "] [" & level & "] " & message
    ts.Close
    
    Set ts = Nothing
    Set fso = Nothing
End Sub

' プログラムの実行開始
Main

4. アーキテクトの視点:さらに高度な「行番号」特定へのアプローチ

上記のコードでは、エラーが発生した際に「何が起きたか」を完璧に記録するように設計しています。しかし、あなたが「どうしても正確な行番号が欲しい!」と願うなら、もう一歩踏み込んだテクニックがあります。

ステップ・トラッキング・パターン

VBScriptにはデバッガが標準で付いていないため、プロの現場では「現在の工程」を明示的に変数に格納する手法を併用します。

Dim currentStep
currentStep = "None"

Sub Step2_ProcessData()
    currentStep = "Step2_ProcessData (Line: 45)" ' 手動でコンテキストを付与
    ' ...処理...
End Sub

これを `HandleError` 内で `WriteLog(“ERROR”, “Location: ” & currentStep)` と呼び出せば、エラーが起きた場所をピンポイントで特定できます。少し手間ですが、これが最も確実で、パフォーマンスへの影響が少ない「実務的な解」です。

パフォーマンスとライフサイクルの注意点

ログ出力を行う際、`FileSystemObject` を毎回 `CreateObject` して閉じるのは、ループ内で大量に行うとパフォーマンスを低下させます。
大規模な処理を行う場合は、「Loggerオブジェクト」をクラス化し、スクリプトの開始時に一度だけ生成(インスタンス化)し、終了時に破棄するという、オブジェクトのライフサイクルを意識した設計にしてください。

まとめ:エラーは「敵」ではなく「教師」である

エラーが出たとき、多くの人は「あぁ、また失敗した」と落ち込みます。しかし、一流のエンジニアは違います。「エラーは、システムの弱点と、自分のコードの改善点を教えてくれる貴重なデータである」と考えます。

今回紹介した「自己検知型ログ設計」をマスターすれば、エラーが発生しても慌てることはありません。ログを見れば、まるで隣でデバッガが動いているかのように、何が起きたのかが手に取るようにわかるはずです。

1. `On Error Resume Next` を使いこなすが、決して過信するな。
2. `Err` オブジェクトの内容を、必ず構造化してログに残せ。
3. 自分自身のパス(`WScript.ScriptFullName`)を武器にせよ。

ここをクリアすれば、あなたのVBScriptは、現場の荒波に耐えうる「本物の自動化ツール」へと昇華します。

さあ、次のコードを書き始めましょう。応援していますよ!

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