Excel VBAのびみょいところ

プログラミングに興味持ってくれた人にVBA教えてたことがあった。
VBA微妙だよみたいなこと言ってたら勉強辞めちゃって悲しかったし、誰も良い気分しないのであんまり言語の悪口とか言わないようにしてる。
けどVBA辛いみたいな話があったのでそれは気のせいじゃないと思うよってなんか書きたくなった。

もちろん良いところもあって、Excel VBAはどこにでもあって、ちょっとした動くものを作るのにちょうどいいと思う。
いつもちゃんとやろうとしてやりすぎることが多い。犬小屋建てるのに高層ビル建設と同じ技術はいらない。

ポエムとExcelへのフォローは以上。
自分の無知から来るところもあると思うので生暖かくお願いします。

# 変数の宣言と同じ行で代入ができない

変数の初期化を2行に分ける必要がある。

```vb
Dim foo As String
foo = "bar"
```

`Dim foo As String = "bar"`みたいには書けない。
コロンで2行の文を1行で書けるので、こうは書ける。

```vb
Dim foo As String: foo = "bar"
```

ただし定数なら1行で書ける。

```vb
Const foo As String = "bar"
```

じゃぁ全部`Const`にすればいいじゃんって思いましたよね。
VBAの`Const`にはほんとに定数(マジックナンバー)しか入れられないので無理です。
変数を使用した値は代入できません。

# 関数(プロシージャ)の宣言方法が3種類ある

- `Sub`: 値を返さない
- `Function`: 値を返す
- `Property`: モジュールのセッタ・ゲッタ

値を返す・返さないで関数の書き方が変わる言語ほかに知らない。

標準モジュールってところにFunctionを作るとExcel側の数式内で使えるようになる。
Subを引数なしで作るとマクロとして実行できる。

# 関数の戻り値を返す方法がreturnじゃない。

```vb
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
```

```vb
Debug.Print greeting(7)
'=> おはよう、たかし！
```

`Function`と同じ名前の変数に代入した値がその戻り値になる。

何が辛いかって
関数を作るたびに少なくとも一回は関数名をコピペしなきゃいけない。
関数名を変えるたびに値を代入してるところ全部置換しなきゃいけない。

だから基本はresultとか適当な変数をつくって最後に代入するんだけど、それでも関数名を変えるたびに置換するのだるい。

例を見て分かるとおり、returnでなく変数に値を代入してるだけなので、そこで処理は終わらず続く。
関数の処理を終わらせたいときは`Exit Sub`または`Exit Function`。

VBAを知ったとき、なんでこういう仕様の言語がほとんどないのか理解した。


# 関数の引数のデフォルトが参照渡し

```vb
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にしてくれてエラーは出ない。

# 戻り値を利用しない関数呼び出しで括弧つけるとエラー

```vb
MsgBox "hello!", vbYesNo

' こう書くとエラー
' MsgBox("hello!", vbYesNo)
```

Call つけて呼ぶとエラーにならない。どっちでもいいでしょ。

```vb
Call MsgBox("hello!", vbYesNo)

' 逆に括弧はずすとエラー
' Call MsgBox "hello!", vbYesNo
```

# オブジェクト型とプリミティブ型で値の代入方法が違う

文字列や数値の変数にはそのまま`hoge = fuga`できる。
オブジェクト型にはSetつけて`Set hoge = fuga`しなきゃだめ。
地味に最初のつまづきポイントだと思う。

```vb
Dim foo
Set foo = Range("A1") 'A1セルが代入される

Dim bar
bar = Range("A1") 'A1の値が代入される
```

オブジェクトを変数に代入したいとき`Set`つけ忘れると`.Value`が参照されるので、これでつまづく人もいそう。
これを避けるためにもなるべく型は明示するのがおすすめ。

ちなみに`Range`の`.Value`はセル1つだとその値、セル2つ以上だとそれらの値の2次元配列が返ってくるのもつまづきポイント。
だけど慣れるとそれを使った小技がけっこうある。

# 複数行のコメントがない
一応、ツールバーに複数行をコメントにするボタンはある。(デフォルトでは隠れてる)

# 複数行に分けて式を書くのが面倒

```vb
Sub hello()
    MsgBox _
      "hello", _
      vbYesNo
End Sub
```

一応、行末にアンダーバーつけると複数行にできるけど、アンダーバーの後や途中の行にコメントかけなかったりコピペ面倒だったりいろいろ辛い。

# 文字列の途中に値を埋め込むのが辛い

変数nの値を埋め込むには`"foo " & n & " bar"`みたいな感じ。
今更だけど文字列を結合する演算子は`&`。

多いなら`${key}`みたいに書いて`Replace`するのがいいかも。こんなん。

```vb
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](https://qiita.com/zakuroishikuro/items/5e4f7f9d0eaba88b1493)

# 日付・時刻の型が浮動小数点数

日付は1900年1月1日からの日数。
Serial Numberと呼びます。日付に番号振ってるだけ。
シリアル値って訳さないほうが分かりやすいと思う。

時刻は1日(24時間)が1.0。
0.5なら昼12時、0.25なら朝6時。

たまに時刻同じように見えてもイコールにならないのは小数計算のズレのせい。

# 条件の「and」「or」がショートサーキットじゃない

```vb
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で時間がかかろうがエラーが出ようが無視していいのに。

だから処理時間の向上やエラー処理の回避を考えると本当はこう書いたほうがいい。よっぽどのことがない限りやんないけどね。

```vb
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で流用できない
# 互換性を維持しすぎて古いまま進化しない、できない、ユーザーが求めてない

---

パッと思いついたのはこんな感じ
疲れたから気が向いたら書く
でもたぶんもう書かないかも