php if文による判定で安全なのは?

いろいろな批評が多いPHPですが、私はいい言語だと思っています。
初心者でも簡単に覚えられるし、一応オブジェクト指向なので。

ただし「型がない」とか「判定がゆるい」とかよく言われています。もちろん厳しく判定することも、型をちゃんと指定してあげることもできます。
なんでも簡単にできる分、判定文一つで大事故につながったりしますので、いろんな意味で楽しいです。

事故怖いですよね
事故怖いですよね

このたび仕事をしていて、あることに謎に興味が湧いてしまったのでコソコソと簡単なスクリプトを作って実験をしてみました。

実験してみたのはif文による判定です。

「何を今更if文など」と情報に強い方は思うかもしれませんが調べてしまったので載せたいと思いました。

ここではどのif文の判定が安全(障害発生しづらい)効率的(処理速度や読みやすさ)なのかを考えてみます。

ここではhasstr()という、簡単な関数を例として作って紹介します。
文字列の入ったデータがあるかどうかを判定し、
文字列がある場合には ” is valid” という文字列を連結して返す、
データが空(””)の場合はfalseを返す、という適当な関数です。

【if文による判定のテストスクリプト】

<?php
$a = "";
$b = "hoge";

function hasstr($str){
    if(empty($str)){
        echo "\$var is empty\n";
        return false;
    }
    $str .= " is valid!";
    return $str;
}


echo "/////////////////////////////////\n";
echo "///①if(!\$output = hasstr(\$a))///\n";
echo "/////////////////////////////////\n";
$output = "";

if(!$output = hasstr($a)){
    echo "\$a NG!\n";
}else{
    echo "\$a OK!\n";
    echo $output."\n";
}

if(!$output = hasstr($b)){
    echo "\$b NG!\n";
} else {
    echo "\$b OK!\n";
    echo $output."\n";
}
echo "/////////////////////////////////\n";
echo "///////②if(!\$output)////////////\n";
echo "/////////////////////////////////\n";
$output = "";

$output = hasstr($a);
if(!$output){
    echo "\$a NG!\n";
}else{
    echo "\$a OK!\n";
    echo $output."\n";
}

$output = hasstr($b);
if(!$output){
    echo "\$b NG!\n";
}else{
    echo "\$b OK!\n";
    echo $output."\n";
}
echo "/////////////////////////////////\n";
echo "///////③if(\$output === false)////\n";
echo "/////////////////////////////////\n";
$output = "";

$output = hasstr($a);
if($output === false){
    echo "\$a NG!\n";
}else{
    echo "\$a OK!\n";
    echo $output."\n";
}

$output = hasstr($b);
if($output === false){
    echo "\$b NG!\n";
}else{
    echo "\$b OK!\n";
    echo $output."\n";
}

?>

①if(!$output = hasstr($a))
この判定文は、コードの量を少しでも減らしたいときに使います。参考書では見かけますが、仕事をしていてあまり見かけません。僕はかっこつけて書こうとしていました。
変数に関数の返り値を代入して、ついでに判定しています。

②if(!$output)
これは$outputという変数に一度hasstr()関数の返り値を代入し、その後にfalseかどうかを判定しています。
これはまあまあ見かけます。①に比べるとコードの行数は増えます。

③if($output === false)
ここでは$outputに一度hasstr()の返り値を代入して、=== による厳密な判定を使ってfalseかどうか判定します。

【①②③の出力結果】

/////////////////////////////////
///①if(!$output = hasstr($a))///
/////////////////////////////////
$var is empty
$a NG!
$b OK!
hoge is valid!
/////////////////////////////////
///////②if(!$output)////////////
/////////////////////////////////
$var is empty
$a  NG!
$b  OK!
hoge is valid!
/////////////////////////////////
//////③if($output === false)////
/////////////////////////////////
$var is empty
$a  NG!
$b  OK!
hoge is valid!

結果は何も変わりませんでした。これがコッソリ書いたテストコードです。問題はなさそうです。
では①で書いたコードの方が良いかと思えばどうやらそうではない模様。

これを同僚に見せた時の反応
①コードは短いけどなんか不安。$aの中に0が入ってたらやばそう(phpの!(否定)は0もnullもfalseも同じように判定されるため)。あまり見かけないから一瞬「?」ってなった。

②よく見かけるけどやっぱり$aに0が入ってきた場合やばそう。読んでいて意味がわかりやすい。普段こういう書き方してる。

③ちょっと堅苦しいけど、厳密な判定をしているし一番安全。意味はわかるけど、普段意識しない書き方。$outputがfalseかそうでないかを見るだけだから処理速度も速いと思う。

①の判定文は見かけない→多分、不具合の要因になりそうなのであまり書かれていないのだと思われます。見慣れない分、レビューなどで突っ込まれそうな印象。

②の判定文は、hasstr()に渡すデータが「0」「null」でないことがわかりきっている場合にのみ使ったほうがよさそうです。それが確実にはわかっていない状況だと、意図しない動きが起きて障害の原因になりそうです。

③はあまりこんな厳密に書いているのを見かけませんが、どんな値が返ってきていたとしても、hasstr()の返り値が「falseか、そうでないか」しか見ていないので!$outputの判定より速度は速く、また、開発者の意図した動きをしてくれそうです。これの方が障害もおきなそう!ということで③の判定を使うことにしました。

障害に強い、不具合を発生させない、安全なシステムを作る場合にはif文であっても判定方法は意識したほうがよさそうです。

phpの場合、他にもemptyやisset,is_nullなどの関数がありますが、必ずしも開発担当の意図どおりに動作してくれるわけではなく、判定するデータによっては不具合を生じさせることが多々あります。

流れてくる可能性のあるデータで全部テストした方が安全です。
必ず意図した動きになるように厳密な判定を意識していこうと思いました。

以上phpによるif文判定で調べてみたことでした。

※間違っていることがありましたらコメントか、twitterなどで連絡ください。