【テクニカル・上級編】VB.NETにおけるModule(モジュール)の功罪:グローバル関数を乱用しないためのオブジェクト指向リファクタリング – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

VB.NETにおけるModuleの功罪:グローバル関数を乱用しないためのオブジェクト指向リファクタリング

レガシーなVisual Basic 6(あるいはVBA)の世界からVB.NETへ移行した開発者が真っ先に手をつけるのが、`Module` キーワードによる「何でも屋の共通関数置き場」の量産だ。どこからでも `Module名.関数名()` で呼び出せるその手軽さは、スクリプト的な開発スピードをもたらす一方で、エンタープライズ領域における保守性を静かに、そして確実に蝕んでいく。

本稿では、VB.NETの `Module` が孕む構造的リスクをメモリ管理、単体テスト、そしてスレッド安全性(Thread Safety)の観点から丸裸にし、真に堅牢なクラス設計へ昇華させるための極限の知見を提示する。

1. なぜ `Module` は危険なのか:アーキテクチャの観点から

`Module` は、IL(Intermediate Language)のレベルにおいて「インスタンス化不可能な、すべてのメンバーが `Shared`(静的)であるクラス」にコンパイルされる。一見すると効率的だが、ここに設計上の致命的な罠がある。

① 隠されたグローバル状態とライフサイクルの不可視性

Module内の変数やメソッドは、AppDomainの生存期間中、メモリ上に常駐し続ける。これは、不要になったリソースをガベージコレクタ(GC)に回収させる契機を奪う。特に、Windows APIのハンドルや、データベースのコネクション文字列などをModuleの静的変数に保持させた場合、メモリリークやマルチスレッド環境での競合(Race Condition)の温床となる。

② 依存性注入(DI)の完全な拒絶

ユニットテストにおいて最大の障壁となるのが、この `Module` である。`Module` 内のメソッドはポリorphism(多態性)を持てないため、インターフェースを通じたモック化(Mocking)が不可能である。外部APIやDBに依存する処理をModule内に直書きした場合、そのコードは「単体テストが物理的に不可能な負債」と化す。

2. 現場で散見される「アンチパターン」の実態

以下は、レガシーなVB.NETコードベースでよく見られる、API呼び出しとファイル操作を詰め込んだ「悪魔のModule」である。

‘ 【アンチパターン】あらゆる処理が詰め込まれた破綻寸前のModule
Public Module SystemUtility

‘ Win32 APIの直接インポート(状態管理の欠如)

Public Function MessageBox(ByVal hWnd As IntPtr, ByVal text As String, ByVal caption As String, ByVal uType As UInt32) As Integer
End Function

‘ グローバルな接続文字列
Public ConnectionString As String = “Server=myServerAddress;Database=myDataBase;Uid=myUsername;Pwd=myPassword;”

‘ ログ出力(静的メソッドの乱用)
Public Sub WriteLog(ByVal message As String)
Dim logPath As String = “C:\Logs\app.log”
System.IO.File.AppendAllText(logPath, DateTime.Now.ToString(“yyyy-MM-dd HH:mm:ss”) & ” – ” & message & vbCrLf)
End Sub

End Module

このコードの問題点は以下の通りだ。

  • `ConnectionString` がグローバルに変更可能なため、マルチスレッド処理時に値が書き換わる危険性がある。
  • `WriteLog` がファイルパスをハードコーディングしており、テスト環境と本番環境で切り替えられない。
  • Win32 APIのラッパーが散在し、メモリの解放やエラーハンドリングが一元化されていない。

3. オブジェクト指向リファクタリング:Moduleからの脱却

上記のアンチパターンを、現代の.NETアーキテクチャに適合した、テスタブルで堅牢なクラス設計へとリファクタリングする。

ステップ1:関心の分離とインターフェースの定義

まずは、外部リソース(ファイル書き込みやAPI)への依存を抽象化する。

‘ ログ出力の抽象化
Public Interface ILogger
Sub Log(message As String)
End Interface

Public Class FileLogger
Implements ILogger

Private ReadOnly _logPath As String

Public Sub New(logPath As String)
_logPath = logPath
End Sub

Public Sub Log(message As String) Implements ILogger.Log
System.IO.File.AppendAllText(_logPath, $”{DateTime.Now:yyyy-MM-dd HH:mm:ss} – {message}{Environment.NewLine}”)
End Sub
End Class

ステップ2:P/Invokeの隠蔽とリソース管理(IDisposableの実装)

Windows APIの呼び出しは、安全なマネージドクラスでラップし、`IDisposable` パターンを用いてハンドルやメモリのライフサイクルを厳密に制御する。

Imports System.Runtime.InteropServices

Public Class Win32ApiWrapper
Implements IDisposable


Private Shared Function MessageBox(hWnd As IntPtr, text As String, caption As String, uType As UInt32) As Integer
End Function

Private _disposed As Boolean = False

Public Sub ShowMessage(hWnd As IntPtr, text As String, caption As String)
If _disposed Then Throw New ObjectDisposedException(NameOf(Win32ApiWrapper))
MessageBox(hWnd, text, caption, 0)
End Sub

‘ IDisposableの実装
Protected Overridable Sub Dispose(disposing As Boolean)
If Not _disposed Then
If disposing Then
‘ マネージド資源の解放
End If
‘ アンマネージド資源の明示的解放(必要に応じてHANDLE閉じるなど)
_disposed = True
End If
End Sub

Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
End Class

ステップ3:依存性注入(DI)による結びつきの弱文化

最終的に、これらのクラスをインスタンスとして生成し、コンストラクタインジェクションを通じて利用する構造に組み立て直す。

Public Class BusinessProcess
Private ReadOnly _logger As ILogger
Private ReadOnly _apiWrapper As Win32ApiWrapper

‘ 依存性を外部から注入(DI)することで、ユニットテスト時にモックを渡せるようになる
Public Sub New(logger As ILogger, apiWrapper As Win32ApiWrapper)
_logger = logger
_apiWrapper = apiWrapper
End Sub

Public Sub Execute()
Try
_logger.Log(“プロセス開始”)
_apiWrapper.ShowMessage(IntPtr.Zero, “処理が完了しました。”, “通知”)
Catch ex As Exception
_logger.Log($”エラー発生: {ex.Message}”)
Throw
End Try
End Sub
End Class

4. チーフアーキテクトからの提言:Moduleを許容すべき唯一の例外

ここまで `Module` の弊害を説いてきたが、シニアエンジニアとして実務上の現実解も示しておüかなくてはならない。VB.NETにおいて `Module` の使用がアーキテクチャ上、唯一許容される、あるいは推奨される特例が「拡張メソッド(Extension Methods)」の定義時である。

VB.NETの仕様上、拡張メソッドは必ず `Public Module` 内に `` 属性を付与して定義しなければならない。

Imports System.Runtime.CompilerServices


Public Module StringExtensions


Public Function IsNullOrWhiteSpace(value As String) As Boolean
Return String.IsNullOrWhiteSpace(value)
End Function

‘ 厳密な型安全性を伴うヘルパー(副作用のない純粋関数のみに限定する)

Public Function ToSafeInt(value As String, defaultValue As Integer) As Integer
Dim result As Integer
If Integer.TryParse(value, result) Then
Return result
End If
Return defaultValue
End Function

End Module

ここで重要なのは、「副作用を持たない純粋関数(Pure Functions)」のみをModuleに許可するという鉄の掟だ。外部の状態を変更せず、入力に対して常に出力が一意に決まるユーティリティであれば、インスタンス化のコストをかけずに安全に利用できる。

結論

VB.NETの `Module` は、レガシーな開発スタイルを引きずるための麻薬である。手軽さの裏に、テスタビリティの喪失、メモリ管理の放棄、そしてマルチスレッドバグという巨額の負債を抱えている。

真にスケーラブルで保守性の高いシステムを構築したいのであれば、今すぐ `Module` の乱用を止め、クラス、インターフェース、そして依存性注入の原則に基づいたオブジェクト指向設計へと舵を切るべきだ。コードの寿命を延ばすのは、安易なグローバル関数ではなく、厳格にカプセル化されたオブジェクトのライフサイクルである。

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