If文のAnd / Or に注意
Javaをメインのプログラム言語とする弊社(株式会社シンメトリック)ですが、もちろん他の言語での開発もやります。以前、ASP(.NETではない)を使ったことがありました。そのとき、VBScriptがJavaと大きく異なる次の2点
- ショートサーキット(後述)する論理演算子がない
- Nullの可能性がある文字列の長さを取る際、事前のNullチェックが要らない
を、自らのバグを通じて学びました。今回は、そのときにハマッた経験談を書きます。
バグ混入の発端
まずはダメなサンプルコードを。やりたかったことは、文字列が入るはずの変数がNullでも空文字列でもない場合だけとある処理を行うという、言語を問わずよくあるプログラムです。
<%@ LANGUAGE = VBSCRIPT %> <html> <head> <% On Error Resume Next Dim var,str '必ずNullを返してしまうファンクション str = GetNull(var) If Not IsNull(str) And Len(CStr(str)) <> 0 Then Call DoImportantProcess(str) %> strを使って重要な処理を行いました。 <% Else %> strがNullか空ですよ! <% End If %>
6行目の「On Error Resume Next」と、12行目の「If Not IsNull(str) And Len(CStr(str)) <> 0 Then」に注目。 「On Error Resume Next」は、実行時エラーが発生しても処理を続けられるというもの。12行目の後半(CStr)にエラーの可能性がありますが、前半のNullチェックでCStrの実行を避けようとしました。ところが、12行目で実行時エラーが発生。更に困ったことに、「On Error Resume Next」が効いて、実行したくない13行目を実行してしまいます。これは大問題。
ショートサーキット評価しないAnd、Or
Javaだったら通常「if (str != null && str.length() != 0) {」と書きます。「&&」は「&」と違い、前の式でFalseと判ったら次の式の評価をせずFalseを返します。これが、余分な評価を省略する「ショートサーキット評価」です。
IsNullで変数のNullチェックをしているのに、どうしてCStrが実行されるの?VBだとJavaのようにならないことに非常に戸惑いました。
MSDNを検索し、演算子の説明や言語間の比較をよくよく読んでみました。すると、二つの式の両方を評価して真偽を返すとの記述が!VBの「And」「Or」はJavaの「&」「|」と似た動きをする。つまりショートサーキット評価しないということです。だからIf文の前半でTrueを返しても後半の式を実行してしまったのですね。
VBにもショートサーキットする演算子はあります。「AndAlso」「OrElse」がそれです。しかし、これはVB.NETとVB2005でサポートされているもので、VBScriptにはありません。
理由が判ったものの対処に困り、If文をネストしてみました。
If Not IsNull(str) Then If Len(CStr(str)) <> 0 Then '大事な処理 %> strを使って重要な処理を行いました。 <% Else %> strがNullか空ですよ! <% End If Else %> strがNullか空ですよ! <% End If
ネストしない方法も試してみましたが・・・
If IsNull(str) Then %> strがNullか空ですよ! <% ElseIf Len(CStr(str)) = 0 Then %> strがNullか空ですよ! <% Else '大事な処理 %> strを使って重要な処理を行いました。 <% End If
どちらのコードも長く、strの値が不正である旨のメッセージを2箇所で記述しています。モッサリしているなぁ。別件でVB.NETを使った際、ショートサーキット演算子のありがたみをヒシヒシと感じ、VBScriptで空文字チェックをするのはとても面倒なことなんだと思いました。
言語間の直訳は危ない
ところが、再度リファレンスで文字列の長さを取るLen関数のところを読み、コードが当初以上に短くなることが判ってしまったのです。
Len(varname) varname:任意の変数の名前を指定します。引数 varname に Null 値が含まれている場合は、Null 値を返します。 Visual Basic Scripting Edition
Javaの場合、strがnullであるかどうか、注意を払ってコーディングします。一方、VBScriptの場合、文字列に対して事前にNullかどうかを確かめることなく、Len関数を呼んでもよいということなのです。結果(Null)を比較した結果はNullなので、If文ではFalseの方を通ります。だから、引数がNullだとエラーが発生するCStrファンクションを使うまでもなく、以下のようにするだけでOK。
If Len(str) <> 0 Then Call DoImportantProcess(str) Else 'strがNullか空の場合の処理 End If
どうですか、このシンプルさ。劇的にシンプルに書けてしまい、喜ぶというより落胆しました(回り道した自分に)。
Javaの「if (str != null && str.length() != 0) {」をVBScriptに置き換える際、直訳をして指を動かしたのがそもそもの失敗。「○○言語ならこう書く」と思ったことを他の言語で書く際、うかつに安直な文法の翻訳をすると私のようにドツボにハマってしまいます。