[備忘録] VBAのデータ型 - Variant型の罠と自分が踏んだ数々のバグたち


## はじめに

![vba-datatype-svg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/a28dfdec-9ab1-40f7-879c-ee84bf004909.png)

VBAを使い始めてから数年、数多くのマクロを書いてきましたが、データ型に関する「あるある」なミスには何度も悩まされてきました。特に万能選手と呼ばれるVariant型は便利な反面、思わぬバグを引き起こすことも...

この記事では、私が実際に経験したVariant型にまつわる「痛い目にあった体験」と、そこから学んだ教訓をメモしておきます。同じ轍を踏まないための備忘録として、また今後のVBA開発の参考になれば幸いです。

## Variant型との出会いと最初の失敗

![vba-variant-mistakes.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/a64c93c0-514b-4ad5-a24f-eeade50e6c2c.png)

私がVBAを始めたばかりの頃、「なんでもできる」Variant型がとても魅力的でした。型宣言の面倒さから解放されると思い、ほぼすべての変数をVariant型で書いていました。

```vb
Sub 昔のコード例()
    Dim データ
    Dim 結果
    Dim カウンタ
    
    ' 何十行もの処理
    ' ...
End Sub
```

ところが、大量データを処理する必要が出てきた時、なぜかプログラムが異常に遅い...調査してみると、Variant型の多用が原因でした。以下は実際に遭遇した例です。

```vb
' 数千行のデータを処理する非効率なコード
Sub 効率の悪いループ処理()
    Dim i
    Dim 合計
    合計 = 0
    
    ' 1万行のデータを処理
    For i = 1 To 10000
        合計 = 合計 + Worksheets("データ").Cells(i, 1).Value
    Next i
    
    MsgBox "処理完了: " & 合計
End Sub
```

このコードは確かに動きましたが、処理に1分以上 (だいぶ過去の記憶では・・・) かかっていました。適切な型を指定することで、驚くほど速くなりました。(大きな違いはあったはず・・)

```vb
' 改善後のコード
Sub 効率の良いループ処理()
    Dim i As Long
    Dim 合計 As Double
    合計 = 0
    
    ' 1万行のデータを処理
    For i = 1 To 10000
        合計 = 合計 + Worksheets("データ").Cells(i, 1).Value
    Next i
    
    MsgBox "処理完了: " & 合計
End Sub
```

**教訓その1:** Variant型は便利だが、大量データ処理には向かない。速度が重要な場合は、適切な型を指定しよう。

## 実際に踏んだ罠たち

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/f5e2ac52-c240-494f-bf79-55bc85291f15.png)

### 罠1: 「=」と「&」の混同による謎の計算結果
あるとき、ユーザー入力を処理するコードで奇妙な現象に遭遇しました。

```vb
Sub ユーザー入力処理()
    Dim 入力値
    Dim 計算結果
    
    入力値 = InputBox("数値を入力してください")
    計算結果 = 入力値 + 100  ' ユーザーが "50" と入力したと仮定
    
    MsgBox "計算結果: " & 計算結果  ' 期待: 150、実際: 150
    
    ' しかし...
    入力値 = "10" + "20"
    MsgBox 入力値  ' 期待: "1020"、実際: 30 ← なぜ？
End Sub
```

文字列の連結には「+」ではなく「&」を使うべきだったんですね。これに気づくまで数時間悩みました...

```vb
' 正しいコード
入力値 = "10" & "20"  ' 結果: "1020"
```

**教訓その2:** 文字列結合には必ず「&」を使う。「+」は数値演算として解釈される。

### 罠2: 空セルとEmptyとNullの混同

空のセルを処理する際、「空なら〇〇」という処理をよく書きますが、ここでも痛い目に...

```vb
Sub 空値処理の失敗例()
    Dim セル値
    
    セル値 = Worksheets("Sheet1").Range("A1").Value  ' 空セルと仮定
    
    ' 空かどうかのチェック...のつもり
    If セル値 = "" Then
        MsgBox "空文字です"  ' ここが実行されると思ったのに...
    Else
        MsgBox "空ではありません：" & セル値  ' なぜかこちら
    End If
End Sub
```

空セルは「""」（空文字）ではなく、特殊な値だったんですね。正しくは：

```vb
' 正しいコード
If セル値 = "" Or IsEmpty(セル値) Then
    MsgBox "空です"
End If
```

もっと厄介だったのが、データベースから取得したNULL値の処理...

```vb
Sub NULL値との格闘()
    Dim DB値
    DB値 = Null  ' データベースからのNULL値と仮定
    
    ' Nullかどうかのチェック...のつもり
    If DB値 = Null Then  ' ← これが間違い！
        MsgBox "NULLです"
    Else
        MsgBox "NULLではありません"  ' なぜかこちらが実行される
    End If
End Sub
```

NULL値は「= Null」で比較できないことを知らず、謎のバグと闘った記憶が...

```vb
' 正しいコード
If IsNull(DB値) Then
    MsgBox "NULLです"
End If
```

**教訓その3:** 空セルやNULL値は専用の関数（IsEmpty, IsNull）でチェックしよう。

### 罠3: 配列操作での混乱

配列を扱う際も、型宣言の違いで大ハマりしました。

```vb
Sub 配列宣言の混乱()
    ' 違いがわからなかった2つの宣言方法
    Dim 配列1 As Variant
    Dim 配列2() As Variant
    
    ' 以下のコードはエラーにならない
    配列1 = Array(1, 2, 3)
    Debug.Print 配列1(0)  ' 結果: 1
    
    ' 以下のコードは実行時エラー！
    ' 配列2(0) = 1  ' 「サブスクリプトが有効範囲にありません」
End Sub
```

配列自体をVariant型に格納する場合と、Variant型の配列を宣言する場合で動作が違うことを理解するまでに時間がかかりました。

```vb
' 正しいコード
Dim 配列2() As Variant
ReDim 配列2(0 To 2)  ' サイズを明示的に指定
配列2(0) = 1
```

**教訓その4:** 配列の宣言には注意。配列変数と配列を格納するVariant変数は別物。

## 業務アプリ開発で実際に起きたトラブル集

![vba-real-troubles.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/90346e2f-44d3-4869-a44a-ec085470275d.png)

### トラブル1: 日付処理の国際問題

海外の同僚と共有するExcelマクロを作っていた時の出来事。日本では動くのに、海外では動かないという謎の現象...

```vb
Sub 日付処理の国際問題()
    Dim 日付文字列
    Dim 処理日
    
    ' 日本での実行（問題なし）
    日付文字列 = "2023/12/31"
    処理日 = CDate(日付文字列)  ' 2023年12月31日として解釈
    
    ' 米国での実行（問題発生！）
    ' 同じコードなのに "2023/12/31" が
    ' "12月31日, 2023年" ではなく "31月12日, 2023年" と解釈される
End Sub
```

原因は地域設定の違い。日付の解釈が国によって異なることに気づかなかったのが敗因でした。

```vb
' 改善策: ISO形式を使用
日付文字列 = "2023-12-31"  ' ISO形式なら国際的に同じ解釈
処理日 = CDate(日付文字列)
```

**教訓その5:** 国際的に使用するマクロでは、地域に依存しない日付形式（ISO形式など）を使おう。

### トラブル2: セル参照の思わぬ罠

大きな表を処理するマクロで、セル範囲の値を取得する際に経験した問題。

```vb
Sub 大量データ処理の罠()
    Dim データ As Variant
    Dim i As Long, j As Long
    
    ' A1:Z1000のデータをVariant配列として取得
    データ = Worksheets("集計").Range("A1:Z1000").Value
    
    ' データ処理（ここで問題発生）
    For i = 1 To 1000
        For j = 1 To 26
            ' インデックスが合わずにエラーに！
            Debug.Print データ(i, j)  ' 実は1から始まるインデックス
        Next j
    Next i
End Sub
```

ValueプロパティでRange値を取得すると、1から始まるインデックスの2次元配列になることに気づかず、ずっとインデックスがずれていました。

```vb
' 正しいコード
For i = 1 To 1000  ' そのまま1から
    For j = 1 To 26  ' そのまま1から
        Debug.Print データ(i, j)  ' これで正しい
    Next j
Next i
```

**教訓その6:** Rangeから取得した配列は、1から始まるインデックスに注意。

### トラブル3: 数式セルの値取得の悲劇

集計用のマクロを作っていた時、数式が入っているセルの値を取得しようとして遭遇した問題。

```vb
Sub 数式セルの悲劇()
    Dim 売上合計 As Variant
    
    ' E10セルに「=SUM(E2:E9)」という数式が入っていると仮定
    売上合計 = Worksheets("売上").Range("E10").Formula
    
    ' 数式自体が取得されてしまう！
    MsgBox "売上合計: " & 売上合計  ' 結果: "売上合計: =SUM(E2:E9)"
End Sub
```

数式ではなく、計算結果の値が欲しかったのに...

```vb
' 正しいコード
売上合計 = Worksheets("売上").Range("E10").Value
```

**教訓その7:** セルの数式ではなく計算結果が欲しい場合は、FormulaではなくValueプロパティを使おう。

## 私なりの対策集
長年の試行錯誤から編み出した、自分なりのVariant型トラブル対策をメモしておきます。
あくまでも説明用の例なので、参考にする場合はアレンジしてください。

### 対策1: 変数名に型を埋め込む

```vb
' 変数名の接頭辞で型を明示
Dim str顧客名 As String
Dim int個数 As Integer
Dim dbl金額 As Double
Dim var汎用データ As Variant  ' Variantだとわかるように
```

これで、コードを読み返す際に変数の型がすぐわかるようになりました。

### 対策2: 自作の安全変換関数

```vb
' 安全な数値変換関数
Function 安全数値変換(入力値 As Variant) As Double
    If IsNull(入力値) Then
        安全数値変換 = 0
    ElseIf IsEmpty(入力値) Then
        安全数値変換 = 0
    ElseIf 入力値 = "" Then
        安全数値変換 = 0
    ElseIf IsNumeric(入力値) Then
        安全数値変換 = CDbl(入力値)
    Else
        安全数値変換 = 0
        Debug.Print "警告: 数値に変換できない値: " & 入力値
    End If
End Function
```

こういった「安全装置付き」の関数を自作して使うようにしています。

### 対策3: デバッグ用の型情報表示マクロ

```vb
' デバッグ用の型情報表示
Sub 変数の型チェック(変数 As Variant)
    Debug.Print "値: [" & 変数 & "]"
    Debug.Print "型名: " & TypeName(変数)
    Debug.Print "VarType: " & VarType(変数)
    Debug.Print "IsEmpty: " & IsEmpty(変数)
    Debug.Print "IsNull: " & IsNull(変数)
    Debug.Print "IsNumeric: " & IsNumeric(変数)
    Debug.Print "IsDate: " & IsDate(変数)
    Debug.Print "IsArray: " & IsArray(変数)
    Debug.Print "IsObject: " & IsObject(変数)
    Debug.Print String(30, "-")
End Sub
```

謎のバグに遭遇したら、まずこの関数で変数の正体を暴きます。

## まとめ - 私が学んだこと

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/5f64c2fd-c87c-4380-ad75-2ee8fabe3b37.png)

Variant型との長い付き合いを通じて学んだことをまとめると：

1. Variant型は便利だが、適材適所で使うべき
2. パフォーマンスが重要な場合は、明示的に型を指定する
3. 空セル、Empty、Nullの違いを理解する
4. 文字列連結には必ず「&」演算子を使う
5. 国際的に使うコードでは地域設定の違いに注意
6. デバッグ用のツールを自作して活用する
7. 変数名に型情報を含めると可読性が上がる

VBAは古い言語ですが、今でも多くの企業で使われています。正しくデータ型を扱えるようになると、バグの少ない安定したアプリケーションが作れるようになります。

過去の自分のような「型で躓く人」が少しでも減れば幸いです。

## 参考にしてきた情報源

- [Microsoft公式: VBAのデータ型](https://docs.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/data-type-summary)