プログラミングに興味持ってくれた人にVBA教えてたことがあった。
VBA微妙だよみたいなこと言ってたら勉強辞めちゃって悲しかったし、誰も良い気分しないのであんまり言語の悪口とか言わないようにしてる。
けどVBA辛いみたいな話があったのでそれは気のせいじゃないと思うよってなんか書きたくなった。
もちろん良いところもあって、Excel VBAはどこにでもあって、ちょっとした動くものを作るのにちょうどいいと思う。
いつもちゃんとやろうとしてやりすぎることが多い。犬小屋建てるのに高層ビル建設と同じ技術はいらない。
ポエムとExcelへのフォローは以上。
自分の無知から来るところもあると思うので生暖かくお願いします。
変数の宣言と同じ行で代入ができない
変数の初期化を2行に分ける必要がある。
Dim foo As String
foo = "bar"
Dim foo As String = "bar"
みたいには書けない。
コロンで2行の文を1行で書けるので、こうは書ける。
Dim foo As String: foo = "bar"
ただし定数なら1行で書ける。
Const foo As String = "bar"
じゃぁ全部Const
にすればいいじゃんって思いましたよね。
VBAのConst
にはほんとに定数(マジックナンバー)しか入れられないので無理です。
変数を使用した値は代入できません。
関数(プロシージャ)の宣言方法が3種類ある
-
Sub
: 値を返さない -
Function
: 値を返す -
Property
: モジュールのセッタ・ゲッタ
値を返す・返さないで関数の書き方が変わる言語ほかに知らない。
標準モジュールってところにFunctionを作るとExcel側の数式内で使えるようになる。
Subを引数なしで作るとマクロとして実行できる。
関数の戻り値を返す方法がreturnじゃない。
Function greeting(currentHour As Long) As String
If currentHour < 9 Then
greeting = "おはよう"
ElseIf currentHour < 15 Then
greeting = "こんにちわ"
Else
greeting = "こんばんわ"
End If
greeting = greeting & "、たかし!"
End Function
Debug.Print greeting(7)
'=> おはよう、たかし!
Function
と同じ名前の変数に代入した値がその戻り値になる。
何が辛いかって
関数を作るたびに少なくとも一回は関数名をコピペしなきゃいけない。
関数名を変えるたびに値を代入してるところ全部置換しなきゃいけない。
だから基本はresultとか適当な変数をつくって最後に代入するんだけど、それでも関数名を変えるたびに置換するのだるい。
例を見て分かるとおり、returnでなく変数に値を代入してるだけなので、そこで処理は終わらず続く。
関数の処理を終わらせたいときはExit Sub
またはExit Function
。
VBAを知ったとき、なんでこういう仕様の言語がほとんどないのか理解した。
関数の引数のデフォルトが参照渡し
Sub subByVal(ByVal n As Long)
n = n + 10
End Sub
Sub subByRef(ByRef n As Long)
n = n + 10
End Sub
Sub exec()
Dim num As Long
num = 10
Debug.Print "[ByVal] before: " & num '=> 10
subByVal num
Debug.Print "[ByVal] after: " & num '=> 10
Debug.Print "[ByRef] before: " & num '=> 10
subByRef num
Debug.Print "[ByRef] after: " & num '=> 20
End Sub
[ByVal] before: 10
[ByVal] after: 10
[ByRef] before: 10
[ByRef] after: 20
仮引数の前にByVal
をつけると値渡し、ByRef
をつけると参照渡しになる。
省略すると参照渡しになる。もちろんByValつけてもオブジェクトは参照渡しになる。
全部ByValつけとけばいいんじゃない?
ちなみに参照渡しのときにAs String
の型にVariant
(any型みたいな感じ)を渡すとエラーになる。
値渡しにしておけば勝手にStringにしてくれてエラーは出ない。
戻り値を利用しない関数呼び出しで括弧つけるとエラー
MsgBox "hello!", vbYesNo
' こう書くとエラー
' MsgBox("hello!", vbYesNo)
Call つけて呼ぶとエラーにならない。どっちでもいいでしょ。
Call MsgBox("hello!", vbYesNo)
' 逆に括弧はずすとエラー
' Call MsgBox "hello!", vbYesNo
オブジェクト型とプリミティブ型で値の代入方法が違う
文字列や数値の変数にはそのままhoge = fuga
できる。
オブジェクト型にはSetつけてSet hoge = fuga
しなきゃだめ。
地味に最初のつまづきポイントだと思う。
Dim foo
Set foo = Range("A1") 'A1セルが代入される
Dim bar
bar = Range("A1") 'A1の値が代入される
オブジェクトを変数に代入したいときSet
つけ忘れると.Value
が参照されるので、これでつまづく人もいそう。
これを避けるためにもなるべく型は明示するのがおすすめ。
ちなみにRange
の.Value
はセル1つだとその値、セル2つ以上だとそれらの値の2次元配列が返ってくるのもつまづきポイント。
だけど慣れるとそれを使った小技がけっこうある。
複数行のコメントがない
一応、ツールバーに複数行をコメントにするボタンはある。(デフォルトでは隠れてる)
複数行に分けて式を書くのが面倒
Sub hello()
MsgBox _
"hello", _
vbYesNo
End Sub
一応、行末にアンダーバーつけると複数行にできるけど、アンダーバーの後や途中の行にコメントかけなかったりコピペ面倒だったりいろいろ辛い。
文字列の途中に値を埋め込むのが辛い
変数nの値を埋め込むには"foo " & n & " bar"
みたいな感じ。
今更だけど文字列を結合する演算子は&
。
多いなら${key}
みたいに書いてReplace
するのがいいかも。こんなん。
Function replaceTags(ByVal text As String, dict As Object)
Dim k As Variant
For Each k In dict.Keys
text = Replace(text, "${" & k & "}", dict(k))
Next
replaceTags = text
End Function
Sub aaaaa()
Dim template As String: template = "${name}(${age})"
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.Add "name", "たかし"
dict.Add "age", 26
Debug.Print replaceTags(template, dict)
'=> たかし(26)
End Sub
文字列で特殊文字を入力(エスケープ)できない
\n
で改行とか\t
でタブとか入力できない。
タブはvbTab
、改行はvbNewline
を文字列の途中に結合する。"1行目" & vbNewline &"2行目"
これほんとにだるい。
それか上記みたいに自分で置換してもいいかも。
ちなみに文字列はダブルクォーテーションのみ。シングルはコメントで使われてるので。
文字列の中のダブルクォーテーションを使いたい場合は重ねる。"a""b"
で a"b
複数行の文字列リテラルがない
素朴に&で結合してくとか、テキストファイルに書いて読み込むとか、Excel側のどこかのセルに書いておくとかして頑張る。
等号が「==」ではなく「=」
もちろんここまで使ってる通り、代入演算子も「=」。
行頭に書くかどうかで代入か等号か決まる。
ちなみに不等号は「!=」じゃなく「<>」。
これで辛いのはVBA書いてるときじゃなくて他の言語に戻ったとき。
if文の条件式の中で代入しちゃったりして見つかりにくいバグが生まれかねない。
True = -1
別に何か不便になるわけじゃないけどすごい。Falseはもちろん0。
Integerの最大値が32767
ちょっと頑張るとすぐ溢れてエラーになる。
Integerに存在意義はないので小さい数値でも脳死でLongだけ使おう。
念のためどっちが計算速いか試したけど同じでした。
VBAで整数をInteger, Long, Variantで扱うときの実行時間の違い #VBA - Qiita
日付・時刻の型が浮動小数点数
日付は1900年1月1日からの日数。
Serial Numberと呼びます。日付に番号振ってるだけ。
シリアル値って訳さないほうが分かりやすいと思う。
時刻は1日(24時間)が1.0。
0.5なら昼12時、0.25なら朝6時。
たまに時刻同じように見えてもイコールにならないのは小数計算のズレのせい。
条件の「and」「or」がショートサーキットじゃない
Sub execA()
If 処理A() And 処理B() Then
Debug.Print "成功!"
End If
End Sub
こういう処理があったとして、並みの言語なら処理Aの結果がfalseなら処理Bは実行されない。
だからJavaScriptで処理A() && 処理B() && console.log("成功!")
みたいに書く人がいる。
VBAは違う。ぜんぶ実行してくれる。
処理Aがfalseなら処理Bで時間がかかろうがエラーが出ようが無視していいのに。
だから処理時間の向上やエラー処理の回避を考えると本当はこう書いたほうがいい。よっぽどのことがない限りやんないけどね。
Sub execB()
If 処理A() Then
If 処理B() Then
Debug.Print "成功!"
End If
End If
End Sub
ループ内で「continue」にあたる文がない
continueなんて軟弱なものはない。
処理全体をif文で囲もう。
ソースに大文字・小文字の区別がない
しかもエディタが勝手に大文字・小文字をあるべき姿に置換しておいてくれる。
Range("a1").value
って書いたらRange("a1").Value
になる。
だけどDim value as Variant
って自分で変数を作ったらValue
になるとこ全部value
に統一される。
だから処理上で問題なくても単語を被らせたくない。
予約語が多い(気がする)
一般的な単語使うとやたら怒られる。あと上記の勝手に大文字小文字が置換される問題もある。
あんまり覚えてないし覚えたくもないからmyValue
とか絶対に被らないものにしてる人が多い印象。
上のほうでcurrentHour
と書いてるのもそれが理由。
VBAはスネークケース使う文化ないので、スネークケースにすると絶対に被らないから個人的に好き。
スタイルガイド?しらね。
三項演算子がない
bool ? trueValue : falseValue
のこと。
使わないほうがいいってよく言われるけどたまにどうしても使いたくなることはある。
一応IIf(bool, trueValue, falseValue)
って書き方がある。
だけどただの関数なのでショートサーキットでない問題を抱えてる。boolがtrueでもfalseValueは実行される。
ないよりマシか。
インクリメント・デクリメントがない
まぁi = i + 1
でいいけど。
複合代入演算子がない
i += 1
したいよ・・・。
除算の余りを求める演算子が「%」でなく「Mod」
10 Mod 3 '=>1
慣れたら別にいいや。
除算の商(整数部分)を求める演算子「¥」がある
別にいいけどなんで剰余の演算子がModなのにそれはあるんだろう?
表計算だとよく使う・・・のか?
配列を動的に拡張しにくい
配列のインデックスの開始値が一定でない
for-each文でVariant型を使うよう強制される
コレクション型の相互変換が面倒
連想配列が使いにくい
正規表現が使いにくい
セルを取得するCellsでサジェストが効かない
Excel側とVBA側で関数名に一貫性がない
VBAで文字列を置換するReplace()にあたるものはExcel関数ではSUBSTITUTE()
Excel関数にはREPLACE関数もあってこれは置換する場所をインデックスで指定する
Excel関数をVBAで呼んで失敗するときいちいちエラー出る
間違ってないんだけどエラー処理が貧弱だからだるい
結合されたセルの最初のセル以外の値は空白扱い
エラー処理が貧弱
エラー時にスタックトレースが見れない
エラー文が不親切
エラーがバグってる
エディタが実質1種類
エディタのデフォルトの設定が使いにくい
エディタで行数が表示できない
ライブラリの参照時に絞り込み検索できない
コンソール(REPL)が独特
print文(Debug.Print)にオブジェクトを渡せない
print文で改行しないようにする書き方がきもい
Debug.Print "abc";
ってセミコロンつけると改行しなくなる。何で?
Excel VBAと他Officeソフトで微妙に仕様が異なる(未確認)
ググって出てくる情報がだいたい古い
ググって出てくる情報がだいたいVBS、VB.NETで流用できない
互換性を維持しすぎて古いまま進化しない、できない、ユーザーが求めてない
パッと思いついたのはこんな感じ
疲れたから気が向いたら書く
でもたぶんもう書かないかも