他言語プログラマ向けのExcelVBA 10の落とし穴

プロジェクトのメイン言語はイマドキのものを使っていたり、
チーム内ではBTSやWeb上での進捗管理をしていても、
顧客とのやり取りや事務処理になんだかんだでExcelやり取りしているところは、
結構あるんじゃないかなと思います。


けど、いざ作業自動化のためにVBAを使おうにも、
今更VBAの本をわざわざ買うってほどではないし、
かといってググってみてもプログラミング初心者向けサイトや、
類似サイトばかりヒットして疲弊するということがあるかもしれません。


というわけで、今回は既に2〜3言語以上は習得しているプログラマが、
初めてExcelマクロを書いたときに躓きやすいポイントをまとめてみました。

Integerは16bitである

範囲は-32768〜32767です。
意図して範囲を狭めているのでなければ32bitのLongを使用しましょう。

整数の割り算で / を使用すると偶数丸めになる

Sub test()
    Dim a As Integer, b As Integer
    a = 5 / 2
    b = 7 / 2
    Debug.Print "a=" & a & ",b=" & b
End Sub

上記のコードを実行すると、結果はa=2,b=4になります。


四捨五入や切り捨てではなく偶数丸めになっているため、
このような挙動になります。
wikipediaによると

端数が0.5より小さいなら切り捨て、端数が0.5より大きいならは切り上げ、端数がちょうど0.5なら切り捨てと切り上げのうち結果が偶数となる方へ丸める

という仕様だそうで、金融界隈ではよく使われているそうです。
ですが、C言語の / と同じような結果を期待している人にとっては、
躓きポイントになりやすいので要注意。
C言語と同じような結果(切り捨て)を求めるのなら、 / ではなく \(円マーク)を
使いましょう。

AndやOrは&&や||相当ではなく&や|相当だ

VBAのAndやOrは多くの言語にある&&や||相当ではありません。
またPythonのAnd,Orとも違います。
どこが違うかというと、Andを使用した場合、
前者の評価がFalseでも後者まで評価してしまう点が異なります。
つまり、他言語では可能だった

if (obj != null && obj.getHoge() > 4)

のような最初にNullチェックをしてその後中身の評価といった
書き方ができません。


以下のコードを実行するとエラーダイアログが表示されます。

Sub test()
    Dim a As Range
    ' セルaの値が1でないときに出力したい
    If Not a Is Nothing And a.Value <> 1 Then
        Debug.Print a.Value
    End If
End Sub


またOrについては、前者がTrueでも後者まで評価してしまいます。
三項演算子相当のIIfの場合はtruePartとfalsePartの両方を評価してしまいます。
VB6ではAndAlsoやOrElseといった構文が新たに用意されているので、
そのうちVBAでも使えるようになるかもしれないです。(Excel2007では使えませんでした)

Forループの終了値指定部は都度評価されるけど再計算はされない

For文のto直後に書く終了値指定ですが、これは繰り返し判定としては評価されますが、
値については最初の1回しか計算されないようです。
これについては日本語で説明するよりもコードを読んだ方が理解が早いと思います。

Sub hoge()
    Dim i As Integer
    For i = 0 To 3
        Debug.Print i
        i = i + 1
    Next i
End Sub

上記コードを実行した場合は、0,2が出力されます。

これはiが3を超えるかをループの都度チェックしているからです。

しかし、以下の例を見てみましょう。

Sub test()
    Dim s As String
    s = "abc"
    Dim i As Integer
    For i = 0 To Len(s)
        Debug.Print i
        s = "abcdefghi"
    Next i
End Sub

上記のコードを実行した場合は、0,1,2,3が出力されます。

これは都度Len(s)を評価しているわけではないためです。

On Errorを同じ関数内で2度使用しない

VBAではファイルアクセスに失敗した時などのために、
エラー処理構文としてOn Error文が用意されています。


しかしこのOn Error、同じ関数内で2度使用すると2回目のエラーはキャッチしてくれません。
以下の例は、処理Aでチャレンジしてみてダメだったら処理Bで試してみる。
それでもダメだったら諦めるといったパターンです。

Sub test()
    Dim a As Range, b As Range
    On Error GoTo 処理スキップA
    MsgBox a.Value
    Exit Sub
処理スキップA:
    MsgBox "処理A失敗のため処理Bでチャレンジ"
    On Error GoTo 処理スキップB
    MsgBox b.Value
    Exit Sub
処理スキップB:
    MsgBox "処理B終了のため、諦める"
End Sub

aとbに何も設定していないため、エラーになるのですが、
2回目は「処理スキップB」に飛ばずにVBAの実行時エラーになってしまいます。

対処としては関数を分けるか、
Resumeを使っていったんエラー情報をリセットしてあげましょう。



今回は文法編だけで終わってしまったので、次回はライブラリ操作の
落とし穴についてピックアップしていきます。
と思ったのですが、もしかしたら飽きてしまい趣味のJava7のネタか、
本業のLinux/C++周りの話を書いているかもしれないです。