[備忘録] Excel VBAでの標準/クラスモジュールの使い分け

Excel VBAの標準モジュールとクラスモジュールの違いを、ポイントに絞って備忘録します。あくまで私個人の判断基準です。ご参考になれば幸いです。

![vba-modules-diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/168e9818-4b6c-4868-809e-296c27abf6da.png)

## 根本的な違い

| | 標準モジュール | クラスモジュール |
|---|---|---|
| **性質** | 静的（1つだけ存在）※ | 動的（複数作成可能） |
| **状態** | グローバル共有 | インスタンス毎に独立 |
| **用途** | 共通処理・ユーティリティ | データ + 処理のセット |
| **初期化** | なし | Class_Initialize/Terminate |
| **イベント** | なし | WithEvents対応 |

※モジュール自体が静的という意味（Static変数とは別概念）

---

## 具体例で理解する

### 標準モジュール：共通のツール箱

```vb
' UtilityModule.bas
Option Explicit
' Option Private Module  ' 他のプロジェクトからの参照を制限

' アプリケーション全体で共有される
Public g_ProcessCount As Long

' 誰でも使える道具（関数）
Public Function FormatMoney(value As Double) As String
    FormatMoney = "¥" & Format(value, "#,##0")
End Function

Public Sub WriteLog(message As String)
    ' ログファイルに出力（共通処理の例）
    Debug.Print Now & ": " & message
End Sub

Public Sub IncrementCount()
    g_ProcessCount = g_ProcessCount + 1
    WriteLog "処理回数: " & g_ProcessCount
End Sub
```

**使い方**: 直接呼び出し
```vb
Debug.Print FormatMoney(1000)  ' ¥1,000
IncrementCount()               ' グローバルカウンターが増える
```

### クラスモジュール：独立したオブジェクト

```vb
' BankAccount.cls
Option Explicit

Private m_Balance As Double
Private m_AccountNumber As String

' 初期化処理
Private Sub Class_Initialize()
    m_Balance = 0
    Debug.Print "口座オブジェクト作成"
End Sub

' 終了処理（リソース解放）
Private Sub Class_Terminate()
    Debug.Print "口座オブジェクト(" & m_AccountNumber & ")削除"
End Sub

Public Property Let AccountNumber(value As String)
    m_AccountNumber = value
End Property

Public Property Get Balance() As Double
    Balance = m_Balance
End Property

Public Sub Deposit(amount As Double)
    m_Balance = m_Balance + amount
End Sub

Public Sub Withdraw(amount As Double)
    If amount <= m_Balance Then
        m_Balance = m_Balance - amount
    End If
End Sub

' イベント発生例
Public Event BalanceChanged(NewBalance As Double)

Private Sub NotifyBalanceChange()
    RaiseEvent BalanceChanged(m_Balance)
End Sub
```

**使い方**: インスタンス作成して使用
```vb
Dim account1 As New BankAccount
Dim account2 As New BankAccount

account1.AccountNumber = "001"
account1.Deposit 1000

account2.AccountNumber = "002"  
account2.Deposit 2000

' それぞれ独立した残高を持つ
Debug.Print account1.Balance  ' 1000
Debug.Print account2.Balance  ' 2000

' Collection で複数管理も可能
Dim accounts As New Collection
accounts.Add account1, "001"
accounts.Add account2, "002"
```

---

## 使い分けの判断

### 標準モジュールを使う場面
```vb
' ✅ 状態を持たない処理
Public Function CalculateTax(price As Double) As Double
    CalculateTax = price * 0.1
End Function

' ✅ アプリケーション全体の設定・定数
Public Const APP_VERSION As String = "1.0"
Public g_LastSavedFile As String

' ✅ エラーハンドリング
Public Sub HandleError(errNum As Long, errDesc As String)
    WriteLog "エラー " & errNum & ": " & errDesc
End Sub
```

### クラスモジュールを使う場面
```vb
' ✅ データと処理がセット
' 顧客情報 + 顧客の操作
' 注文情報 + 注文の処理
' ファイル情報 + ファイル操作

' ✅ 同じ種類のものを複数扱う
' 複数の顧客、複数の注文、複数のファイル

' ✅ イベント駆動が必要
' WithEvents を使った通知パターン
```

---

## 実践例：同じ処理での比較

データ処理を例に、実装の違いを見てみましょう。

### 標準モジュール版
```vb
' DataProcessor.bas
Public g_ProcessedCount As Long  ' 全体で共有

Public Sub ProcessData(data As String)
    ' 処理実行
    g_ProcessedCount = g_ProcessedCount + 1
    Debug.Print "処理済み件数: " & g_ProcessedCount
End Sub
```

**問題点**: 
- 複数の処理を同時に行うと、カウンターが混在する
- 前の処理の影響を受ける

### クラスモジュール版
```vb
' DataProcessor.cls
Private m_ProcessedCount As Long  ' インスタンス毎に独立

Public Property Get ProcessedCount() As Long
    ProcessedCount = m_ProcessedCount
End Property

Public Sub ProcessData(data As String)
    ' 処理実行
    m_ProcessedCount = m_ProcessedCount + 1
    Debug.Print "処理済み件数: " & m_ProcessedCount
End Sub
```

**利点**:
```vb
Dim processor1 As New DataProcessor
Dim processor2 As New DataProcessor

processor1.ProcessData "データA"  ' processor1のカウント: 1
processor2.ProcessData "データB"  ' processor2のカウント: 1

' それぞれ独立してカウント
Debug.Print processor1.ProcessedCount  ' 1
Debug.Print processor2.ProcessedCount  ' 1
```

---

## まとめ

### 選択の指針（判断フローチャート）

```
データや状態を保持する必要がある？
├─ No  → 標準モジュール（ユーティリティ関数）
└─ Yes → 複数のインスタンスが必要？
          ├─ No  → 標準モジュール（グローバル変数）
          └─ Yes → クラスモジュール
```

### パフォーマンス面の注意点

**標準モジュール**
- ✅ 呼び出しコストが低い（1回ロードで使い回し）
- ❌ グローバル変数の乱用でメモリリーク的バグが発生しやすい

**クラスモジュール**  
- ✅ 独立した状態で安全
- ❌ インスタンス作成にわずかなコストあり

### 覚えておくべき1つのルール

**「複数作る可能性があるか？」**

- **No** → 標準モジュール
- **Yes** → クラスモジュール

あくまで私個人の判断基準です。ご参考になれば幸いです。

## 参考

https://learn.microsoft.com/en-us/openspecs/microsoft_general_purpose_programming_languages/ms-vbal/4599fae2-3f41-4e70-968e-2398741f446b

https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/modifiers/withevents

https://learn.microsoft.com/en-us/openspecs/microsoft_general_purpose_programming_languages/ms-vbal/62bbe63e-379c-4dc0-8648-7d9050a2f396

https://support.microsoft.com/ja-jp/topic/vba-%E3%83%9E%E3%82%AF%E3%83%AD%E3%82%92%E5%8B%95%E7%9A%84%E3%81%AB%E8%BF%BD%E5%8A%A0%E3%81%97%E3%81%A6%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8Bvisual-basic-0e717419-427e-f4c4-0d09-df52aaab8bca