【実務・中級編】初心者向け:VB.NETにおけるModule(モジュール)の正しい使い方と、グローバル変数を乱用しないための設計指針 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

VB.NETのModule(モジュール)とグローバル変数の罠:実務で「動くが壊れやすい」コードを撲滅する設計指針

開発現場で業務効率化ツールや社内ニッチシステムをVB.NETで構築する際、誰もが一度は誘惑に駆られるものがある。
それが、`Module` キーワードを用いた共通関数の定義と、どこからでもアクセスできるグローバル変数(Public変数)の乱用だ。

「とりあえずモジュールに `Public g_DbConnection As SqlConnection` と書いておけば、どのフォームからも使い回せて楽だ」
――この設計、あなたのプロジェクトの寿命を確実に縮めている。

今回は、VB.NETのモジュールが持つ本来の存在意義と、グローバル変数が引き起こす悪夢のようなバグのメカニズムを解き明かす。そして、オブジェクト指向の原則に基づいた「保守性が高く、テスト可能な真に堅牢なコード」への脱却手法を、現場のチーフアーキテクトである私個人の知見を交えて伝授しよう。

1. なぜModuleとグローバル変数は「諸刃の剣」なのか

VB.NETにおける `Module` は、内部的に「インスタンス化不可能な、すべてのメンバーが `Shared`(静的)であるクラス」としてコンパイルされる。C#には存在しないVB特有の構文であり、手続き型言語(旧Visual BasicやC言語など)の感覚でコードを書けるため、初心者やスピード重視の開発者には非常に重宝されてきた。

しかし、ここに大きな落とし穴がある。

グローバル変数が引き起こす3つの大罪

1. 状態の追跡不能(Spaghetti State)
どのメソッドが、いつ、どのタイミングで変数の値を書き換えたのかがコードを追うだけでは分からなくなる。バグが発生した際、「誰がこの値を `null` に変えたのか?」を特定するために何時間もデバッグに費やす羽目になる。
2. マルチスレッドや非同期処理への完全な脆弱性
業務ツールであっても、ファイルI/OやDBアクセスの非同期化(`Async` / `Await`)を行う現代において、静的領域(メモリ上の固定領域)にある変数は競合状態(レースコンディション)を引き起こし、予測不可能なクラッシュを誘発する。
3. 単体テスト(Unit Test)の不可能化
グローバル変数に依存したメソッドは、「他の状態」に依存するため、特定のコンテキストをモック(模擬)することができない。テストコードが書けない=品質を担保できない、ということだ。

2. Moduleの正しい使い所:純粋関数(Pure Functions)の置き場

では、モジュールは一切使ってはいけないのか? 答えは「NO」だ。
モジュールは、「状態を持たず、入力値に対して常に同じ出力値を返す(副作用のない)純粋関数」をまとめる場所として使うべきである。

典型的な例が、文字列操作、日付計算、独自のログフォーマット変換といったユーティリティ関数だ。

【推奨】正しいモジュールの実装例

Namespace BusinessTools.Utilities
‘ 状態(フィールド変数)を一切持たないユーティリティモジュール
Public Module StringUtility

”’

”’ 文字列がNullまたは空白の場合にデフォルト値を返します。
”’ 副作用のない純粋な関数です。
”’


Public Function NullToDefault(value As String, defaultValue As String) As String
If String.IsNullOrWhiteSpace(value) Then
Return defaultValue
End If
Return value
End Function

”’

”’ ファイルパスとして使用できない不正な文字を除去します。
”’

Public Function SanitizeFileName(fileName As String) As String
Dim invalids As Char() = System.IO.Path.GetInvalidFileNameChars()
Return String.Concat(fileName.Where(Function(c) Not invalids.Contains(c)))
End Function

End Module
End Namespace

このように、インスタンス変数を一切定義せず、引数として渡されたデータのみを処理して結果を返すメソッドだけを配置するのであれば、モジュールは非常に有効な選択肢となる。

3. データベース・ファイル連携におけるアンチパターンと正解

業務効率化ツールで最も多い失敗が、データベース接続や設定情報をグローバル変数で保持する設計だ。

【アンチパターン】やってはいけないグローバル管理

‘ 絶対にやってはいけない例
Public Module AppData
Public ConnectionString As String = “Server=myServerAddress;…”
Public CurrentUser As String

‘ グローバルなコネクションの保持は、接続リークや予期せぬ切断の元凶
Public DbConnection As System.Data.SqlClient.SqlConnection
End Module

この設計では、アプリのどこからでも接続を閉じたり開いたりできてしまい、トランザクションのスコープが崩壊する。

【プロダクションコード】依存性注入(DI)とスコープ管理を取り入れた設計

実務では、データアクセスの責務を専用のクラス(リポジトリ)に閉じ込め、using ステートメント等で確実にリソースを解放するライフサイクル管理を行うべきだ。

以下のコードは、設定ファイルから安全に接続情報を読み込み、安全にDBからデータを取得する堅牢なクラス設計の例である。

Imports System.Data.SqlClient
Imports Microsoft.Extensions.Configuration

Namespace BusinessTools.Data

‘ データアクセスの責務を持つクラス(モジュールではない)
Public Class UserRepository
Private ReadOnly _connectionString As String

‘ コンストラクタインジェクションにより、接続文字列を外部から注入する
Public Sub New(connectionString As String)
If String.IsNullOrWhiteSpace(connectionString) Then
Throw New ArgumentException(“接続文字列が空です。”, NameOf(connectionString))
End If
_connectionString = connectionString
End Function

”’

”’ ユーザー一覧を安全に取得するメソッド
”’

Public Function GetActiveUsers() As List(Of String)
Dim users As New List(Of String)()
Dim query As String = “SELECT UserName FROM Users WHERE IsActive = 1”

‘ Usingブロックを使用し、例外発生時でも確実にコネクションとコマンドを破棄する
Using connection As New SqlConnection(_connectionString)
Using command As New SqlCommand(query, connection)
Try
connection.Open()
Using reader As SqlDataReader = command.ExecuteReader()
While reader.Read()
users.Add(reader(“UserName”).ToString())
End While
End Using
Catch ex As SqlException
‘ 実際のプロジェクトではここで適切なロギングを行う
Throw New InvalidOperationException(“データベースからのユーザー取得に失敗しました。”, ex)
End Try
End Using
End Using

Return users
End Function
End Class

End Namespace

4. グローバル変数からの脱却:アプリケーション状態の管理戦略

どうしてもアプリケーション全体で共有したい設定値や状態(ログインユーザー情報や環境設定など)がある場合はどうすべきか。
答えは「シングルトンパターン(Singleton)」または「設定管理クラス(Options Pattern)」の採用である。

状態のスコープを明確にし、「どこからでも書き換えられる状態」を排除する。

Namespace BusinessTools.Configuration

‘ アプリケーションのコンテキストを安全に保持するクラス
Public NotInheritable Class AppSession
‘ Lazy(Of T) を用いたスレッドセーフなシングルトン実装
Private Shared ReadOnly _instance As New Lazy(Of AppSession)(Function() As New AppSession())

Private Sub New()
‘ 外部からのインスタンス化を禁止
End Sub

Public Shared ReadOnly Property Current As AppSession
Get
Return _instance.Value
End Get
End Property

‘ 読み取り専用、または内部で制御されたプロパティとして公開
Public Property LoginId As String
Public Property LoginTime As DateTime

Public Sub Initialize(userId As String)
Me.LoginId = userId
Me.LoginTime = DateTime.Now
End Sub

Public Sub Clear()
Me.LoginId = Nothing
Me.LoginTime = Nothing
End Sub
End Class

End Namespace

呼び出し側のコード

‘ セッションの初期化(ログイン時)
AppSession.Current.Initialize(“emp12345”)

‘ 参照(どこからでも安全に読めるが、変更は意図したメソッド経由に制限される)
Dim currentUser As String = AppSession.Current.LoginId

このように、生のエディタブルなグローバル変数ではなく、カプセル化されたセッション管理クラスを挟むことで、データの整合性が劇的に向上する。

まとめ:保守性の高いコードを書くためのマインドセット

VB.NETはその簡潔さゆえに、「動けば正義」のスパゲッティコードを生み出しやすい言語だ。特に初学者や、VBAからのステップアップ組が陥りがちな `Module` とグローバル変数の乱用は、システムの拡張性を奪い、将来の自分(または後任のエンジニア)を苦しめる最大の負債となる。

今日から意識すべき設計指針は以下の3点だ:
1. Moduleは「状態を持たない純粋なユーティリティ関数」の置き場としてのみ使う。
2. データベースやファイルなどのリソースは、`Using` を使って確実かつ最短のライフサイクルで解放する。
3. グローバル変数は排除し、必要な状態はコンテキストクラスやプロパティ経由で安全に管理する。

「手っ取り早く書く」ことの代償は高い。堅牢で美しいコードベースこそが、結果的に最も開発効率を加速させる最強の武器であることを忘れないでほしい。

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