VBAへの不満点16個と対処法について

最近仕事でVBAを触る機会が多くなってきました。

VBAは他の言語と比べると貧弱で、書いててストレスが溜まることがあります。

その不満点と対処法について個人的にまとめたので、他言語から触る人向けに書いていきます。(結構長いです)

目次

continue文がない

自分も結構驚いたんですが、VBAにはcontinue文がありません。

ですがgoto文は存在するので以下のようにラベルを使えばcontinueの動きを実装できます。

For i = 0 to 10

    If i = 9 Then
        Goto L_CONTINUE
    End If

    Debug.Print i

L_CONTINUE:

Next i


完全に余談ですが、goto文を使えばFor文も使わずにプログラムできそうですね。
気になったので以下、C言語で九九を表示するプログラムを書いてみました。

void kuku()
{
    int i, j;
    
    i = 1;
    BEGIN1:
        if (i == 3) goto END1;
        j = 1;
        BEGIN2:
            if (j == 2) goto CONTINUE2;
            
            printf("%2d ", i*j);
        CONTINUE2:
            j++;
            if (j < 10) goto BEGIN2;
        END2:
        
        puts("");
        
    CONTINUE1:
        i++;
        if (i < 10) goto BEGIN1;
    END1:
    
    puts("end");
}

1 3 4 5 6 7 8 9
2 6 8 10 12 14 16 18
end
と出力されます。

break文がない

VBAにはbreak文はありませんが、代わりにExit Forという構文が存在します。

For i = 0 To 10
    Debug.Print i
    If i = 0 Then
        Exit For ' ここで抜ける
    End If
Next i

While文だとExit Doになります。

return文がない

これも最初見たとき驚きました。

ですがreturn分相当の動きを再現することは一応できます。

関数名に返したい値を代入し、Exit Functionを書きます。

Function Hoge()
    Dim a as Integer
    a = 10

    If a Mod 2 = 0 Then
        Hoge = 0
        Exit Function
    End If

    Hoge = a

End Function

ですが、関数名変更するたびに書き替える必要があって面倒くさいです。

オブジェクトを変数に入れる時Set文が必要

Javaなんかだとプリミティブ型でもオブジェクト型でも同じ構文で変数に代入できますが、VBAはオブジェクトを代入するとき以下のように書く必要があります。

Set hoge = Range("A1")

他の言語の感覚で書いてると謎のエラーが出るので要注意です。

関数呼び出しにカッコが不要

VBAで関数を呼び出す際は以下の2つの構文があります。

FuncName a, b, c

Call FuncName(a, b, c)

他の言語の感覚で関数名に括弧を付けるとコンパイルエラーが発生するので要注意です。

また、他の関数の引数にするときは以下のように括弧が必要になります。

Func1 Func2(1, 2, 3)

括弧を付けないとエラーが発生します。

未宣言の変数を参照しても例外が発生しない

これのせいで変数名のタイプミスを見つけるのに時間がかかりました。
例えば以下のような場合、エラーが発生しません。

Sub Foo()
    hoge = 10
    If hove = 10 Then ' ここでタイポしている
        Exit Sub
    End If
    Debug.Print 100
End Sub

Option Explicitとファイルの頭に書けば変数の宣言を強制できるので、モジュールを追加するときは忘れずに書くのをお勧めします。

エラー処理が貧弱

VBAでエラー処理するときはOn Error Goto という構文を使います。

エラー発生時に指定したラベルに移動する感じですね。

これによってエラー発生時の挙動を定義することができるようになります。

ただ問題点があって、これを使うとエラー発生時に行数が表示されなくなります。

関数が小さい場合は良いですが、大きく複雑になってくると地獄です。

まずDebug.Printでどの箇所がエラーなのかを探す作業が始まるからです。

また、スタックトレースとかも無いのでVBAデバッグ作業は他の言語と比べると大変な部類な気がします。

複合代入演算子(+=, -=, *=, /=, …)がない

VBAでは他の言語のように a += 1 と書けません。

代わりに以下のように書く必要があります。

a = a + 1

当然インクリメントとかも無いので結構面倒です。

また、長い文字列を代入するときも以下のようになって不便です。

(VBAで文字列連結するときは&を使う)

s = s & ""

s = s & ""

個人的には、他の言語みたいに s += "" と書きたいです。テキストブロック(三連引用符)があれば最高ですね。

配列周りがめんどくさい(Redim Preserve...)

VBAでは動的配列と静的配列があります。

まず、静的配列ですが仕様がかなり独特です。例えば

Dim arr(10) As Integer

と定義したとき、0~10までの要素数「11」の配列が宣言されます。

また、宣言するとき要素数には変数を使用できません。(C言語と同じですね)

それなのにmallocみたいな感じで領域確保とかもできません。


続いて動的配列についてですが、これは静的配列と違って要素数が可変な配列になります。

結構不便です。

要素を追加するときに以下のように書く必要があるからです。

Dim arr() As Integer
Redim arr(0)

arr(0) = 1

Redim Preserve arr(1)

arr(1) = 2

他の言語のように arr.append(2) みたいな感じでは書けません。

なので結局ラッパー関数を定義する必要が出てきます。

こんなかんじですね。

Sub Arr_Add(arr As Variant, value As Variant)
    
    ReDim Preserve arr(UBound(arr) + 1)
    arr(UBound(arr)) = value
    
End Sub

でも毎回定義するのは面倒くさいです。


また対処法は他にもあって、配列を捨てCollectionを使うというものです。

そうすると以下のように書けます。

Set seq = New Collection

seq.Add 1

seq.Add 2

問題点としては、Joinの引数に使えなかったりRangeに代入できなかったりする点です。


他にも.NETのArrayListを使用することもできます。

配列周りの関数(Sort, Uniq, Reverse, Shuffle, ...)はVBAだと貧弱なので結構便利です。

以下のように使います。

Set arr = CreateObject("System.Collections.ArrayList")

arr.Add 20
arr.Add 10
arr.Add 0

arr.Sort 

Debug.Print Join(arr.ToArray)
' => 0 10 20

Set arr = Nothing

VBAの動的配列に変換するにはToArrayメソッドを使用します。


また、動的配列に要素を一括で代入したい場合は以下のようにします。

' Dim arr as Variantでも可
Dim arr() As Variant

arr = Array(1, 2, 3 ,4 ,5)

Debug.Print Join(arr)
'=> 1 2 3 4 5

Array関数を使うことで、簡単に代入できます。Redimも使わずに行けます。

クロージャが無い

ここまで見た人はだいたい想像つくと思いますが、クロージャなんかも当然ありません。

個人的にはPythonとかでmapとかfilterを使いまくってるので結構不便に感じます。

一応map相当の関数は、メソッドを名前から呼び出せるCallByNameという関数を使えば実装できますけどね。

エクセル付属のIDEの挙動が微妙におかしい

これはユーザーフォームを作っているときによく感じます。

ユーザーフォームのイベントを書いているとき、コードを実行するとエディタの画面が勝手にフォームのポトペタ画面に切り替わります。

実行するたび毎回毎回エディタの画面を切り替える必要があるので、かなりストレスが溜まります。

この動きをさせない設定とかもありません。

しかもユーザーフォームを書いてなくてもこの挙動をする時があります。


また、他にもイミディエイトウィンドウというのがあって、これはREPLみたいな感じでコード片を実行するものなんですが、地味に不便です。

Pythonシェルみたいな感じではなく、テキストエディタに実行機能がついたって感じなので、実行するたび文字列が溜まってきて一々消す必要があります。

コンストラクタに引数がつけられない

VBAには一応Class構文が存在します。

他にも何故かインターフェースとかもあるらしいですが、クラス同士の継承はサポートされていません。

そしてコンストラクタに引数を付けることもできません。

対応策としては、別途で初期化用のメソッドを定義するくらいじゃないでしょうか。

こんなかんじですかね。

Dim obj As New Hoge

obj.Init(a, b, c, d)

個人的には Dim obj = New Hoge(a, b, c, d) と書きたいです。


論理演算子が短絡評価されない

これができないと無駄にIF文がネストすることになったりして不便です。

例えば、配列の添え字チェックをしつつ配列の要素を比較したいときですかね。

If idx <= UBound(arr) And arr(idx) = 10 Then
    ' Do Nothing
End If

他の言語の場合、idxが要素数を超えた時点でfalseが返されますが、VBAだとそのまま評価が継続され arr(idx) のところでエラーが発生します。

こういう場合はIF文をネストさせるしかありません。

配列の要素にアクセスするときは角括弧を使えない

他の言語(C系列)では配列の要素にアクセスする際は arr[0] という形で記述しますが、VBAの場合は arr(0) と括弧を使って記述します。

変数の宣言と代入が同時に出来ない

VBAでは変数を宣言する際には以下のように書きます。

Dim hoge As Integer

ですが、Dim hoge As Integer = 10 のように書くことはできません。

他の言語なら int a = 10 のようなかんじで初期化できるので結構不便です。

一応対処法として以下のように書くことはできます。

Dim hoge As Integer: hoge = 10

2行に分けて書いていたのを1行にまとめることができます。

比較演算子のイコールが「==」ではない

VBAでは値同士の比較をするとき = を一つだけ使用します。

他の言語だと a == b というところをVBAだと a = b と書きます。

これが不便なのは以下のようなときですね。

hoge = a = b

変数hogeにaとbを比較したBoolean値を代入するときです。

個人的には hoge = a == b と書きたいです。

また、このような構文になっているせいか a = b =  c = 10 のように一括で代入することはできません。


以上