【テクニカル・上級編】【レガシー保守】スパゲッティコード化した巨大 VBScript を「Class 構文」で再構築するリファクタリング・ガイド – VBScript (Visual Basic Scripting Edition)解析バイブル

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

リファクタリングの深淵:スパゲッティVBScriptを「Class構文」で解体・再構築する極限の設計論

現場のシニアエンジニア諸君、あるいは、数千行に及ぶ「動くが、誰も触れない」VBScriptの化石に立ち向かっている管理者諸君。

我々が直面しているのは、単なるコードの乱れではない。それは、「状態(State)」と「振る舞い(Behavior)」が未分化のまま、グローバルスコープという泥沼に沈殿した、技術的負債の極致である。

手続き型で書かれた巨大な `.vbs` ファイル。至る所に散らばる `Dim` 宣言。副作用を撒き散らすグローバル変数。条件分岐の迷宮。これらを放置することは、将来のシステム崩壊を予約することと同義だ。

本稿では、単なる「書き換え」ではない。VBScriptにおけるオブジェクト指向への転換、すなわち Class構文を用いたアーキテクチャの再構築 について、メモリ管理からWindows APIとの境界線に至るまで、その真髄を解き明かす。

1. 手続き型コードが陥る「決定論の喪失」

なぜ、手続き型の巨大なスクリプトは壊れるのか? 理由は単純だ。「副作用の予測不能性」にある。

手続き型コードでは、ある関数(Sub/Function)が、意図せずグローバル変数を書き換え、それが全く無関係な別の処理の挙動を狂わせる。この「隠れた依存関係」が、コードの規模に比例して指数関数的に増大していく。

破綻の兆候

  • 変数のスコープが広すぎる: `Dim` がスクリプトの冒頭に並び、どこで値が変わったか追跡できない。
  • 単一責任原則の欠如: 一つの `Sub` が、ファイル操作、ログ出力、DB接続、計算までをすべて担っている。
  • エラーハンドリングの不在: `On Error Resume Next` が乱用され、異常が発生しても「何が起きたか」を検知できないまま処理が進む。

これらを打破する唯一の手段が、カプセル化(Encapsulation)である。

2. Class構文による「境界線」の確立

VBScriptにおける `Class` は、単なるコードの整理術ではない。それは、「データ」と「それを操作するロジック」を一つの閉じた世界に閉じ込めるための防壁である。

リファクタリングの第一歩は、既存の処理を「何をするものか」という概念(ドメイン)に基づいてクラスへと切り出すことだ。

設計の黄金律

1. プロパティは最小限に: 外部から直接書き換えられる変数は極力減らし、`Property Get` / `Property Let` を通じて制御せよ。
2. メソッドは自己完結させる: クラスのメソッドは、外部の状態に依存せず、クラス内部のプロパティのみを用いて完結させるべきである。
3. コンストラクタの役割を明確にする: `Class_Initialize` で、オブジェクトが「正しい状態」から始まることを保証せよ。

3. メモリ最適化とオブジェクトのライフサイクル

VBScript(WSH)環境において、エンジニアが最も軽視しがちなのが、COMオブジェクトのライフサイクル管理である。

VBScriptは参照カウント方式でメモリを管理している。巨大なループ内で `FileSystemObject` や `WScript.Shell` などのCOMオブジェクトを生成し、適切に解放しなければ、メモリリークを引き起こし、長期間稼働するバッチ処理においてシステム全体の不安定化を招く。

鉄則:明示的解放

オブジェクトへの参照が不要になった瞬間、直ちに `Nothing` を代入せよ。

' 悪い例:スコープが切れるまでメモリに残り続ける
Set objFSO = CreateObject("Scripting.FileSystemObject")
' ... 処理 ...

' 良い例:使用直後に解放する
Set objFSO = CreateObject("Scripting.FileSystemObject")
' ... 処理 ...
Set objFSO = Nothing ' 参照カウントを確実に減らす

クラス設計においては、`Class_Terminate` を活用し、クラスが破棄される際に内部で保持しているCOMオブジェクトも確実に解放する設計を徹底すること。

4. 実践:リファクタリング・パターン

ここでは、バラバラに書かれた「ファイル操作」と「ログ記録」のコードを、どのようにクラスへ統合すべきか、具体的なコード例を示す。

【Before】スパゲッティ状態(手続き型)

' どこで何が変わるか分からない、典型的なレガシーコード
Dim logFile, targetDir, errorCount
logFile = "C:\logs\app.log"
targetDir = "C:\data"
errorCount = 0

On Error Resume Next
Set fso = CreateObject("Scripting.FileSystemObject")
Set ts = fso.OpenTextFile(logFile, 8, True)

' ファイル操作ロジックが混在
Dim files, f
Set files = fso.GetFolder(targetDir).Files
For Each f In files
    If f.Name = "config.tmp" Then
        f.Delete
        If Err.Number <> 0 Then
            ts.WriteLine Now & " [ERROR] Delete failed: " & Err.Description
            errorCount = errorCount + 1
            Err.Clear
        End If
    End If
Next

ts.Close
Set ts = Nothing
Set fso = Nothing
WScript.Echo "Errors: " & errorCount

【After】アーキテクチャ再構築(Class構文)

クラスを分割し、責任を明確化する。

' ============================================================
' Logger Class: ログ出力の責任のみを持つ
' ============================================================
Class Logger
    Private logPath
    Private ts

    Private Sub Class_Initialize()
        ' 初期化ロジック
    End Sub

    Public Sub Open(path)
        On Error Resume Next
        Set ts = CreateObject("Scripting.FileSystemObject").OpenTextFile(path, 8, True)
        If Err.Number <> 0 Then
            WScript.Echo "Critical Error: Cannot open log file."
            WScript.Quit
        End If
        On Error GoTo 0
    End Sub

    Public Sub Write(message)
        If Not ts Is Nothing Then
            ts.WriteLine Now & " [" & message & "]"
        End If
    End Sub

    Public Sub Close()
        If Not ts Is Nothing Then
            ts.Close
            Set ts = Nothing
        End If
    End Sub

    Private Sub Class_Terminate()
        Close
    End Sub
End Class

' ============================================================
' FileProcessor Class: ファイル操作のビジネスロジックを担う
' ============================================================
Class FileProcessor
    Private fso
    Private logger

    Private Sub Class_Initialize()
        Set fso = CreateObject("Scripting.FileSystemObject")
    End Sub

    ' 依存性の注入 (Dependency Injection)
    ' 外部からLoggerを渡すことで、テスト容易性と結合度を制御する
    Public Sub SetLogger(logObj)
        Set logger = logObj
    End Sub

    Public Function CleanTempFiles(targetDir)
        Dim errorCount, files, f
        errorCount = 0
        
        If Not fso.FolderExists(targetDir) Then
            If Not logger Is Nothing Then logger.Write "DIR NOT FOUND: " & targetDir
            CleanTempFiles = 0
            Exit Function
        End If

        Set files = fso.GetFolder(targetDir).Files
        For Each f In files
            If f.Name = "config.tmp" Then
                On Error Resume Next
                f.Delete
                If Err.Number <> 0 Then
                    If Not logger Is Nothing Then logger.Write "DELETE FAILED: " & f.Name & " - " & Err.Description
                    errorCount = errorCount + 1
                    Err.Clear
                End If
                On Error GoTo 0
            End If
        Next
        
        CleanTempFiles = errorCount
    End Function

    Private Sub Class_Terminate()
        Set fso = Nothing
        Set logger = Nothing
    End Sub
End Class

' ============================================================
' Main Execution Logic
' ============================================================
Sub Main()
    Dim myLogger, myProcessor
    Dim errs

    ' オブジェクトの生成
    Set myLogger = New Logger
    Set myProcessor = New FileProcessor

    ' 依存関係の設定
    myLogger.Open "C:\logs\app.log"
    myProcessor.SetLogger myLogger

    ' 実行
    errs = myProcessor.CleanTempFiles("C:\data")

    WScript.Echo "Process Completed. Total Errors: " & errs

    ' 明示的な破棄(Class_Terminateが走り、内部のCOMも解放される)
    Set myProcessor = Nothing
    Set myLogger = Nothing
End Sub

Main

5. 極限の知見:Windows APIとの境界線

VBScript単体では、レジストリの高度な操作や、プロセス制御、低レイヤーのメモリ操作には限界がある。伝説的なアーキテクトは、ここで無理にVBScriptだけで解決しようとはしない。

もし、業務要件がVBScriptの限界を超えているならば、以下の戦略を取るべきだ。

1. COMコンポーネントの活用: 複雑なロジックやWindows APIを多用する部分は、C#やC++でCOMサーバー(DLL)として実装し、VBScriptからは `CreateObject` で呼び出す。
2. WScript.Shellによるブリッジ: `WshShell.Run` を使い、PowerShellや高度なコマンドラインツールを「カプセル化されたメソッド」として実行する。

VBScriptはあくまで「オーケストレーター(指揮者)」であるべきだ。重い楽器(低レイヤー操作)をすべてVBScriptに演奏させるのではなく、適切な専門家に指示を出す。これが、レガシー環境における真のアーキテクチャ設計である。

結びに代えて

リファクタリングは、単なる「コードの掃除」ではない。それは、混沌としたシステムに「秩序」をもたらす聖なる儀式である。

Class構文を使いこなし、オブジェクトのライフサイクルを支配し、責任の所在を明確にする。この原則を貫くことで、君たちが遺すコードは、次世代のエンジニアにとっての「資産」となるだろう。

幸運を。

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