スパゲッティの深淵から脱却せよ:VBScriptを「Class構文」で再構築するアーキテクチャ設計術
業務自動化の現場において、我々は避けて通れない「怪物」に直面することがある。
数千行に及ぶ、延々と続く `If…Then…Else` の迷宮。どこからともなく湧いてくるグローバル変数の汚染。一箇所を修正すれば、全く関係のない箇所でエラーを吐き出す、予測不能な副作用。そう、「手続き型で書き殴られた、巨大なスパゲッティ・スクリプト」だ。
多くの開発者は、これを「VBScriptの限界」だと諦める。しかし、それは大きな間違いだ。VBScriptには `Class` 構文が存在する。このオブジェクト指向の概念を正しく導入できるか否かが、単なる「スクリプト作成者」と、信頼される「システムアーキテクト」を分かつ境界線となる。
本稿では、レガシーなVBScriptを、保守可能で堅牢な「クラスベース・アーキテクチャ」へと昇華させるための極限の知見を伝授する。
1. なぜ、あなたのコードは「壊れやすい」のか
リファクタリングを始める前に、敵の正体を理解せよ。スパゲッティコードがなぜ致命的なのか。理由は主に3つある。
グローバル変数の氾濫(状態の不透明性)
プロシージャ間で共有される変数があちこちで書き換えられる設計は、プログラムの「状態」を予測不可能にする。どの関数がいつ変数を変えたのか、追跡することはもはや不可能だ。
責務の混濁(Single Responsibility Principleの欠如)
「ファイルを読む」「計算する」「ログに書く」「画面に出す」……これらが一つの巨大な `Sub` や `Function` に詰め込まれている。これでは、ファイル形式が変わっただけで、計算ロジックまで修正しなければならなくなる。
エラーハンドリングの不在
`On Error Resume Next` を魔法の杖のように使い、エラーを握りつぶす。これは「バグを隠蔽している」だけであり、本番環境での爆弾を製造しているに等しい。
2. クラスによる「境界線」の構築
リファクタリングの核心は、「データ(状態)」と「振る舞い(ロジック)」を一つのユニット(クラス)に閉じ込めることにある。
VBScriptの `Class` を使う目的は、単にコードを整理することではない。「外部から干渉できない、独立した部品」を作ることにある。
設計の黄金律
1. カプセル化: プロパティは可能な限り `Private` にし、変更は専用の `Method` を通じて行う。
2. 依存性の注入 (Dependency Injection): クラス内で直接 `FileSystemObject` を生成するのではなく、外部から渡す、あるいはコンストラクタで初期化する設計を意識せよ。これにより、テストが容易になる。
3. 単一責任: 「ログ出力クラス」はログを書くことだけに集中させる。「CSV解析クラス」は解析だけに集中させる。
3. 実戦:堅牢なプロダクションコード・パターン
では、具体的な実装例を示そう。
「CSVファイルを読み込み、特定の条件でデータを抽出し、結果をログに出力する」という業務シナリオを想定する。
以下のコードは、単なるサンプルではない。「エラーハンドリング」「オブジェクトのライフサイクル管理」「責務の分離」を組み込んだ、実戦投入可能なアーキテクチャだ。
' ==========================================================================
' Project: Enterprise Automation Refactoring Guide
' Description: Class-based architecture for robust VBScript execution
' ==========================================================================
Option Explicit
' --- Main Execution Flow ---
' プログラムの入り口。ロジックを直接書かず、コントローラーを呼び出す。
Sub Main()
Dim logger, processor, csvPath, targetValue
csvPath = "C:\temp\data.csv"
targetValue = "Active"
' 1. インスタンス化 (Dependency Injectionの準備)
Set logger = New Logger
Set processor = New DataProcessor
' ログの初期化
logger.Initialize "C:\temp\process.log"
On Error Resume Next ' 実行レベルのエラーハンドリングを開始
logger.Info "--- Processing Started ---"
' 2. 業務ロジックの実行
' processorにloggerを渡すことで、processorはログ出力機能を利用できる
Set processor.Logger = logger
If processor.ProcessFile(csvPath, targetValue) Then
logger.Info "Process completed successfully."
Else
logger.Error "Process failed. Check the source file or format."
End If
If Err.Number <> 0 Then
logger.Error "Critical System Error: " & Err.Description
End If
logger.Info "--- Processing Ended ---"
' 3. クリーンアップ (オブジェクトのライフサイクル管理)
On Error GoTo 0
Set processor = Nothing
Set logger = Nothing
End Sub
' ==========================================================================
' CLASS: Logger
' 責務: ファイルへのログ記録
' ==========================================================================
Class Logger
Private fso
Private logFile
Private logPath
' コンストラクタ的な初期化メソッド
Public Sub Initialize(path)
Set fso = CreateObject("Scripting.FileSystemObject")
logPath = path
' 追記モードでファイルを開く
Set logFile = fso.OpenTextFile(logPath, 8, True)
End Sub
Public Sub Info(msg)
WriteLog "INFO", msg
End Sub
Public Sub Error(msg)
WriteLog "ERROR", msg
End Sub
' Privateメソッドにより、外部からの不適切な書き込みを防止
Private Sub WriteLog(level, msg)
If Not logFile Is Nothing Then
logFile.WriteLine "[" & Now & "] [" & level & "] " & msg
End If
End Sub
' デストラクタに相当するクリーンアップ
Public Sub Close()
If Not logFile Is Nothing Then
logFile.Close
Set logFile = Nothing
End If
Set fso = Nothing
End Sub
End Class
' ==========================================================================
' CLASS: DataProcessor
' 責務: CSVの解析とビジネスロジックの実行
' ==========================================================================
Class DataProcessor
' 外部クラスへの参照(依存性)
Public Logger
Private fso
Private Sub Class_Initialize()
Set fso = CreateObject("Scripting.FileSystemObject")
End Sub
' メインのロジック
Public Function ProcessFile(filePath, filterValue)
Dim file, line, parts
ProcessFile = False ' デフォルトは失敗
' ファイルの存在確認
If Not fso.FileExists(filePath) Then
If Not Logger Is Nothing Then Logger.Error "File not found: " & filePath
Exit Function
End If
Set file = fso.OpenTextFile(filePath, 1)
Do Until file.AtEndOfStream
line = file.ReadLine
If line <> "" Then
' 簡易的なCSV分割(実務ではカンマ区切りを考慮)
parts = Split(line, ",")
' フィルタリングロジック
If UBound(parts) >= 1 Then
If parts(1) = filterValue Then
If Not Logger Is Nothing Then Logger.Info "Match Found: " & parts(0)
' ここに実際のデータ処理(DB登録など)を記述する
End If
End If
End If
Loop
file.Close
Set file = Nothing
ProcessFile = True ' すべて正常に終了した場合のみTrueを返す
End Function
Private Sub Class_Terminate()
Set fso = Nothing
End Sub
End Class
' プログラム実行
Main
4. プロフェッショナルが守るべき「細部」の規律
コードを書く際、以下の3点に魂を込めろ。これこそが、保守性の差を生む。
I/Oリソースの厳格な管理
`FileSystemObject` や `ADODB.Connection` などのリソースは、使い終わったら即座に `Set obj = Nothing` し、ファイルなら `.Close` を呼び出せ。VBScriptのガベージコレクションを過信するな。リソースの解放漏れは、長時間稼働する自動化ツールにおいて「メモリリーク」と「ファイルロック」という名の死を招く。
エラーハンドリングの階層化
`On Error Resume Next` は、「エラーが予想される最小単位のスコープ」でのみ使用せよ。
クラス全体を `On Error Resume Next` で覆うのは愚策だ。エラーが発生した場所を特定できなくなる。エラーが発生したら、そのクラス内で適切に処理(ログ出力)を行い、呼び出し元に「失敗した事実」を戻り値(Boolean等)で伝えるのがプロの設計だ。
依存関係の可視化
上記の例では、`DataProcessor` が `Logger` をプロパティとして持っている。これは「`DataProcessor` はログ機能に依存している」ことを明示している。このように、クラス間の関係性を明確にすることで、将来的に「ログをファイルではなく、データベースに書き換えたい」となった際、`Logger` クラスだけを差し替えれば済むようになる。
結びに代えて
VBScriptは、決して「使い捨てのスクリプト言語」ではない。
適切な設計思想を持ち、`Class` を駆使して構造化すれば、企業の基幹業務を支える極めて強力な武器へと進化する。
「動けばいい」という考えを捨てろ。
「次にこのコードを読む人間が、迷わず、安心して修正できるか?」
この問いに答えられる設計こそが、真に価値のある自動化エンジニアリングである。
