73
40

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Excel VBAのびみょいところ

Last updated at Posted at 2022-06-30

プログラミングに興味持ってくれた人に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で流用できない

互換性を維持しすぎて古いまま進化しない、できない、ユーザーが求めてない


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

73
40
16

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
73
40

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?