PHPで闇深いといわれる代表選手は ==
ですが、これについては使わなければいいだけですので、そこまで目くじらを立てる必要はなさそうです。
個人的に闇深いと感じたのが false
の使われ方です。
規定の型 or false
PHPの関数によくある返り値が規定の型 or false
というパターン。
代表的なのがstrpos。
返り値
needle が見つかった位置を、 haystack 文字列の先頭 (offset の値とは無関係) からの相対位置で返します。 文字列の開始位置は 0 であり、1 ではないことに注意しましょう。
needle が見つからない場合は FALSE を返します。
これがなかなか扱いづらいのです。
アノテーション
そういった関数のラッパー的な関数を書くときにこの流儀を引き継ぐと、アノテーションを書くときに
/**
* @return int|boolean
*/
みたいな感じになるわけですが
PHPdocのreturnでよく見かける@return array|bool
— ちょなそ (@chonaso) 2017年10月12日
って、@return array|false
じゃねーの?っていつも思う。
といった心の叫びが漏れてしまってました。
Union Type V2では int|false
みたいな書き方が許されてるようですね。
お前どっちのfalseよ!?
phpredisってインタフェース設計がほんとクソ。
— ちょなそ (@chonaso) 2018年10月31日
getやhGetで値の設定がない時にfalseを返すんだけど、これじゃ値がbooleanの場合はまずexistsやhExistsしなきゃならないからアトミックにならない。
つまりRedisにbooleanの値が格納されていることが期待できる状況でfalse
が返ってきた場合、値がセットされていないのかfalse
という値が取れたのか区別がつかないわけです。
これに対してこういうコードを書きたくなるわけですが、これはgetしたときには値が消されている可能性が否定できないのでアウトです。
$value = $redis->exists('hoge') ? $redis->get('hoge') : getFromDatabase('hoge');
ワークアラウンドとして、boolean型の値を取得したい場合は一度自動シリアライズを無効にするってところでしょうか。
// デシリアライズしない $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); // redis上でbooleanのfalseが格納されていたら b:0 が返ってくる、存在しなかったら booleanのfalseが返ってくる。 $ret = $redis->get('hoge'); // シリアライズ設定戻す $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); // Redisから値が取得できている場合は手動でデシリアライズする $value = ($ret !== false) ? unserialize($ret) : getFromDatabase('hoge');
こういった事情からこんなコードを書きたくなってしまうわけですが、
if($ret = $redis->get('hoge')){
$value = unserialize($ret);
}else{
logger.debug("******************");
$value = getFromDatabase('hoge');
}
これはさすがにIDEにはやめろと言われます。
ダメなの?(テストは通る)
— ちょなそ (@chonaso) 2017年12月8日
PHPがアレなのかNetBeansがアレなのか… pic.twitter.com/3mtDDD3RwZ
とりあえずfalseでなきゃOK的な
他にもあるかもしれませんが。ユニットテストにassertNotFalseなんてのが存在のするはPHPだけじゃなかろうか。
— ちょなそ (@chonaso) 2018年4月17日
おまけ
これは true
null
にも言えることなんですが、大文字・小文字の区別が無いという点もなかなか闇深事案ですね。
PHPマニュアル内でも一貫性が無いという徹底ぶりです。