C#ばっかり触ってきた筆者が、VBAを触って発狂している記事です。
型システムがおわっている
「Variant型」という謎の型
Variant型という、なんとも形容しがたい型があります。
雰囲気としては、C#の「dynamic型」に近いです。
本当に何でも代入できる型で、代入時に型が決まるような挙動をします。
そして別の型を再代入すればその型に代わります。つまり何が入っているか、まったくわからない!
コード補完はきかないし、型チェックも行われない。
これだけなら「へー」で住むのですが、Variant型にしか代入できないことが多々あります。
配列でFor Eachを使う場合
1 2 3 4 | Dim item As Variant For Each item In array() MsgBox item Next |
String型の配列でも、ForEachで使うitemはVariant型にしなければなりません。
String型で宣言するとエラーになります。
そしてこてはVariant型であってString型ではないので、この「item」をString型の引数に直接は設定できません。「CStr(item)」などとして、明示キャストが必要があります。
「Object型」という謎の型
似たように何でも代入できる型に、object型という型もあります。
こちらもC#のObject型に似てる…けど、Integerなどを代入できないので、どちらかというとJavaっぽい。
ただし、特にキャストしなくてもメンバを参照できるdynamic型のような挙動もします。
違う型を入れて共通のメソッドを呼び出す、ダックタイピング的なことができるのは少し面白いです。
参照型はSetで代入する。
数値型とかのプリミティブ(?)な型はそのまま代入します。
1 | X = 10 |
参照型はSetをつけないと実行時エラーになります。
1 | Set X = Range( "A1:Z10" ) |
参照型と値型分けるのはいいんですけど、こんな型ゆるゆるなのにそこ気にする?という感想です。
ちなみに値型は
1 | Let X = 123 |
のLetを省略したものらしい。しらんがな
配列がおわっている
添え字の「0」始まりと「1」始まりが混在する。
VBAでは、添え字が何から始めるか指定することができます。
以下のように書くと1から始まり
1 | Dim arr(1 To 10) As String |
なにもしないと、0から始まり
1 | Dim arr(10) As String |
エクセルのRangeなどは1始まります。
1 | Range( "A1:Z10" ) |
頭おかしくなっちゃう
配列長がとれない
VBAでは配列の長さがとれません。
一応、間接的にとることができます。
1 | arrayLength = UBound(array) - LBound(array) + 1 |
でもこれ、配列の長さが0だとエラーになります。
例外処理が終わってる
「例外処理もできますよ」という顔をしながら、実際はとてつもなくやりにくいです。
「例外が起こるとGoToみたいにラベルジャンプする」というしょうもない機能が付いています。
1 2 3 4 5 6 7 | Sub Hoge() On Error GoTo error 'なんかの処理 Exit Sub error: '例外処理 End Sub |
なんか、書き方が違うだけでtry-catch構文みたいなので悪くないように思えますが、もう一度言います。
やってることは「エラーになったらラベルジャンプする」です。
「Exit Sub」なんてつけてますが、(returnみたいに関数を終わらせる)
これがないと普通に下の方に流れて正常な場合も例外処理になります。
色々構文があって、エラー処理した後にもとの行に戻るなんてこともできてしまいますが、所詮はラベルジャンプです。
1 2 3 4 5 6 7 | Sub Hoge() On Error GoTo error 'なんかの処理 error: 'エラー処理 Resume Next '←次の行から再開させる End Sub |
そして、ラベルジャンプなので複数の例外処理をネストできません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | '※動きません Sub Hoge() On Error GoTo error1 'なんかの処理1-1 'なんかの処理1-2 On Error GoTo error2 'なんかの処理2 Exit Sub error2: '例外処理2 End Sub Exit Sub error1: '例外処理1 End Sub |
もうだめです
goto使っちゃダメっておばあちゃんに言われたでしょ。
プロシージャが終わってる
呼び出し方に統一性がない
まず、VBAにはプロシージャ(関数とかメソッド)が2種類あります。
SubとFunctionです。
値を返さないのがSubで、返すのがFunctionです。
書き方も違えば呼び方も違います
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | Sub サブルーチン 'なんかの処理 End Sub Sub 引数ありサブルーチン(hooge As integer) 'なんかの処理 End Sub Function ファンクション As String 'なんかの処理 Set ファンクション = "いろは" End Sub Function 引数ありファンクション(hooge As integer) As String 'なんかの処理 Set 引数ありファンクション = "いろは" End Sub |
問題なのは、呼び出し方
返値無しのSubは、()もなにもつけず呼び出します。
引数がある場合も()をつけずに記述します。
1 2 | サブルーチン 引数ありサブルーチン 123 |
一方、返値無しのFunctionは、()をつけて記述します
1 2 | Hoge = ファンクション() Hoge = 引数ありファンクション(123) |
そして、返値を使わない(代入しない)場合は「Call」と書かなければなりません
1 2 | Call ファンクション() Call 引数ありファンクション(123) |
修正するときただただメンドウ
返値の書き方が独特
C系の言語では、returnで値を返して関数を終わらせます。
VBAでは、値を返す構文と、関数を終わらせる構文は別です。
関数を終わらせるのには「Exit Sub」か「Exit Function」です。
ここでも、SubとFunctionで書き方が違ってメンドウです。
1 2 3 4 5 6 7 | Sub Hoge Exit Sub End Sub Function Fuga As Integer Exit Function End Function |
返値は、その関数名の変数に代入します。
1 2 3 4 | Function Fuga As Integer Fuga = 2 Exit Function End Function |
プロシージャの名前を変える場合、この部分も変更しないとエラーになります。
嘘です。デフォルトだと宣言していない変数にも代入できるのでエラーにすらなりません。
※Option Explicitを頭につけましょう。
短絡評価がない
短絡評価とは、複数の真偽判定AとBを組み合わせたときに、
Aの真偽によってはBを判定(=実行)しない仕組みです。
たとえば、あるセルが数式エラー(#N/Aとか)か空文字だったら処理するコードを考えます。
1 2 3 4 5 | If isError(cell) OR cell.Value = "" Then MsgBox "セルが数式エラーか空文字だよ" Else MsgBox "セルの値は" & cell.Value & "だよ" End If |
上のコード、実はエラーになります。
なぜなら「cell」が数式エラーのセルの場合、
cell.Valueを実行しようとした段階でエラーとなるからです。
上のコードを正しく書くと、以下のようになります。
1 2 3 4 5 6 7 | If IsError(cell) Then MsgBox "セルが数式エラーか空文字だよ" Else If cell.Value = "" Then MsgBox "セルが数式エラーか空文字だよ" Else MsgBox "セルの値は" & cell.Value & "だよ" End If |
コードが長くなってしまいました。
短絡評価の機能があれば、とてもシンプルにかけます。
1 2 3 4 5 | If isError(cell) OrElse cell.Value = "" Then MsgBox "セルが数式エラーか空文字だよ" Else MsgBox "セルの値は" & cell.Value & "だよ" End If |
IsError(cell)が真となった時点で、右側のcell.Valueの値に関わらず全体の評価は真になるので
cell.Valueは実行されない―つまり呼び出せないメソッドを呼ぼうとしてエラーになったりしません。
VBには短絡評価があるそうですが、VBAにはありません。あきらめましょう。
その他色々
変数の宣言と代入が同時にできない。
変数を宣言するだけで、普通の言語の2倍の行を使います。
1 2 | Dim X As Integer X = 12 |
Continueがない
そのままの意味です。For文などでContinueが使えません。
検索するとgoto文使えって言われます。ひどい。
変数のスコープが変(というか、ない)
関数内でスコープが作られません。
なので以下のコードは変数名重複でエラーになります。
1 2 3 4 5 6 7 8 9 | If A Then Dim x As Integer x= 3 End If If B Then Dim x As Integer x= 10 End If |
For文もスコープが作られなません。
そもそもイテレータの宣言を文の外でしなければならない仕様上、イテレータの変数も別名で用意する必要があります。
1 2 3 4 5 6 7 8 9 10 11 | Dim i As Long For i = 0 to 10 Dim x As Integer x= 3 End If Dim i As Long 'イテレータも被るのでiは無理 For i = 0 to 10 Dim x As Integer 'ここもxは無理 x= 3 End If |
Rangeとか何のシートか省略 ”できてしまう”。
なんも気にせず「Range」などと書くと、現在アクティブなエクセルファイルの現在アクティブなシートを参照します。
個人的にはわかりずらいので、この仕様はやめてほしいです。
大文字と小文字を区別しない
区別しない上に、VBAの開発環境で勝手に揃えられます。
正規表現が不自由
詳しくは書きませんが、VBAで使える正規表現のライブラリ、後読みができません
1 2 3 | 'こういうやつ (?<=パターン) (?<!パターン) |
名前付きグループや非キャプチャグループなも作れず、正規表現方法の古さを感じます。
そもそもVBAに改善が見込めない
マイクロソフトはもう長い間VBAのメンテナンスをしていません。
後継の言語を実装しようという動きもありません。
つまり、上記の問題点が改善されることをはありません。
一応、ブラウザ版のエクセルには、JSやTypeScriptで書けるOffice Scriptなるものがあるようです。
そちらが逆輸入される日を夢見てます。