【VBA】論理演算子(And, Or)の注意点 短絡評価の話

VBAの論理演算子(And, Or)を使用するときの注意点として短絡評価が行われないということが挙げられます。

例えば下記のようなコードを書くと実行時にエラーが発生する可能性があります。

' コレクションの要素をイミディエイトウィンドウに表示する
Private Sub PrintItems(ByVal cl As Collection)
    If Not (cl Is Nothing) And cl.Count > 0 Then
        For Each itm In cl
            Debug.Print itm
        Next
    End If
End Sub

先述の短絡評価が行われないことが原因なのですが、今回はこの件について書きます。

短絡評価とは

下の表のようにAND演算では入力Aが0のとき、OR演算では入力Aが1のときは入力Bが何であっても出力の値が定まります。

入力A入力B出力
000
010
100
111
AND演算 真理値表
入力A入力B出力
000
011
101
111
OR演算  真理値表

短絡評価とは

  • 「条件式1 And 条件式2」
  • 「条件式1 Or 条件式2」

というような式を評価するときに、左辺(条件式1)の結果で式全体の結果が決まる場合は右辺(条件式2)を評価しないというものです。

ショートサーキットとも呼びます。

前半の結果により後半の評価が不要となればコンピュータに余計な処理をさせなくて済むというメリットがあります。

VBAでは短絡評価しない

VBAでは短絡評価行いませんつまり左辺(条件式1)だけで式全体の結果が決まったとしても右辺(条件式2)まで評価します。

これを知った上でコーディングしないと問題を発生させてしまうこともあるので覚えておきたいです。

ちなみにVB.NETではAnd, Orの他にAndAlso, OrElseという名前の演算子が用意されていて短絡評価を行うことができます。

実行時エラーに注意

冒頭のサンプルコードについて解説します。

' コレクションの要素をイミディエイトウィンドウに表示する
Private Sub PrintItems(ByVal cl As Collection)
    If Not (cl Is Nothing) And cl.Count > 0 Then
        For Each itm In cl
            Debug.Print itm
        Next
    End If
End Sub

引数で受け取ったCollectionオブジェクトがNothingでなく、要素数が0よりも大きい場合にFor EachでイミディエイトウィンドウにCollectionオブジェクトの要素全てを表示します。

このコードでは引数のCollectionオブジェクト「cl」がNothingだった場合に実行時エラーが発生します。

なぜなら、短絡評価しないのでCollectionオブジェクト「cl」がNothingだった場合でも、右辺(cl.Count > 0)が評価されてしまうからです。clがNothingなのにCountプロパティにアクセスしようとして落ちてしまいます。

構文は間違えていないので「VBAProjectのコンパイル」を行っても問題なく通ります。実行時でないとエラーが発生しないので厄介です。

実行時エラーが発生しないように書き直すと下記のようになります。

' コレクションの要素をイミディエイトウィンドウに表示する
Private Sub PrintItems(ByVal cl As Collection)
    If Not (cl Is Nothing) Then
        If cl.Count > 0 Then
            For Each itm In cl
                Debug.Print itm
            Next
        End If
    End If
End Sub

あまりスマートな書き方とは言えません。ネストも深くなって微妙です。

パフォーマンスへの影響

短絡評価が行われないことによってパフォーマンスへ影響することもあります。

If func1 And func2 Then    ' func1, func2はそれぞれBoolean型の値を返す関数とします。
    MsgBox "処理が正常に完了しました", vbOKOnly
End If

上記サンプルコードにおいて、func2はfunc1の結果に関わらず必ず実行されます。func1がFalseを返した場合は、func2の実行は単純に無駄な処理(※1)となります。

func2の処理が軽ければ良いのですが、もしfunc2の処理が重たくて値を返すのに時間が掛かる場合は、下記のように書き方を見直した方が良いです。

If func1 Then
    If func2 Then
        MsgBox "処理が正常に完了しました", vbOKOnly
    End If
End If

(※1)func2が副作用の無い関数の場合の話です。関数内部でメンバ変数やグローバル変数の値を書き換えたり、ファイルへの書き込みを行うなど、何か状態を変化させる関数は副作用がある関数となります。

最後に

普段他の言語を使って開発している人がたまにVBAをさわると嵌まってしまう内容だと思います。気を付けたいです。

タイトルとURLをコピーしました