「is_null()を使うか === nullを使うか」と何気なく聞いてみたら面白かった

と軽い気持ちでツイートしてみたら知見が寄せられて面白かった話。
「全然その辺りは好みの問題じゃないかな〜?」というのがあったんですが、1発目・2発目からガッチリ論拠のある意見をもらって「おぉ、なるほど・・・」となった次第。

そもそも

PHP 8 で作る JSON パーサ / php8-json-parser - Speaker Deckを見ていて、「お! ===だ!」と思って気になったんですよね〜。 該当コードは↓。 github.com

(ちなみに発表内容がすごく面白かったです、今だとLIVE配信のアーカイブ観れますが恐らく後日セッションごとに切り分けられたものが?上がると思うので、動画のリンクは止しておきます)

自分的に「is_null の方が読みやすくない?」って思っているフシがあったので、さてどっちだろうな〜と。

※ 他の方のツイートを埋め込み表示にするの何となく気がひけるので、リンクの明示だけにしておきます

わたしは

is_null() を使う事が多いな〜と思っていて、

  • null と「同じ」である、という表現が何となく気持ち悪いような
    • これ多分SQLのせいだなーって話をしている内に思った
  • 自分は「名前がついている処理」はなるべくソレを使いたいな〜という性質がありそう
    • 「それが用意された」のに相応の理由があるのかな、という気がする
    • 関連して「目立たせておくべき」で「書き換えor読み間違いにくい」というような気もする

落とし所としては

  • 書き方が揃っている ことが最重要な気がする、それさえ抑えていれば「些細な問題」に収まる範疇なのかな?という感覚
  • それを決めるための「論拠」があった方が良い、というのは間違いないです!
    • === null の方が「しっかり」してそうだな、って話を聞いてて思った
  • また、こういうのを「ルール」にしたら(人の力に頼らず)linterに仕事させたい!!

ってことで、以下寄せられた意見

=== null

パフォーマンス的な面

FQSENで記述しないとオーバーヘッドが大きくなる」というもの。
このレスが速攻で飛んできて「すげぇ!」「ありがてぇ!」ってなってしまったw
(いや本当に、反応もらえたとしてもゆるふわっとした感じだと思ってたので。)

PHP的に is_null($v); は2通りの解釈ができて、

  1. 自身が属するnamespaceにおける関数、 ns\is_null() というもの
  2. グローバル(組み込み関数)としてのis_null、すなわち \is_null() というもの

もし \is_null() と明示的に呼ばれていれば問題は起こらないが、そうでない場合、まず1のis_nullを探してから2のis_nullを呼ぶ、というものになる。

もちろん use function is_null; と書けば問題はない、が、面倒くさいのでは・・・?と(個人的に)思った。
それだったら最初から === null でいいじゃん、というもの。

コレ自体は「言われてみればそうだな、確かに確かに」と思ったんだけど、補足のリプで「opcodeを追ってみればこんなに自明」という風に情報を示されて、「なにそれ面白い早く俺もそっちに行きたい」という感情を刺激されてしまった・・・・

また、別観点として「そもそも明示的に \hoge() と書くの良いかもな〜」というのも思った。
誤読が少ないし、分かりやすい。

ちなみに、逆に言うとルート空間の明示を省略して書くことで「関数をぶっ壊す」というのも可能で、実際にfile uploader周りのテストを書く時に「is_uploaded_file()を無理やりtrueにする」みたいな処理を過去に使ったことがある。

<?php

namespace Vendor\Package\FileUploader\Uploade {
    fnuction is_uploaded_file($filename)
    {
        return true;
    }
}

みたいな。

シンプルさの面

if (is_null($v)) とした時に、

  1. まず is_null: bool がチェックされて
  2. 次に if (bool) が チェックされて

とか何なん!的なもの。(って書き方だとニュアンス伝わりにくい、「関数をコールするよりは式のほうがシンプルじゃないか」的な)

また、「他の値(例えば is_true())の時もそうやってやるんか??」と言われ、なるほど〜見方によっちゃ揃ってないな?という。
これもまたなるほど・・・

is_int とか is_string とかは使い道ありそうだけど、「nullかどうか」は「null以外はnullじゃないの」となるので、違和感があるな〜とも思った。
「値が1つしか存在しない」というのを前提にすると「nullを特別扱いするか?」が恐らく感覚の境目になってきて、

  • nullは特別だからis〜 でやろうよ
  • is〜を使うことで「値が1つ以上あるから、値の種類で判定したい」みたいに誤読しない?

という対立する判断に進みそうかもな。報われない気持ちを整理したくなるな。

そういや全く関係ないけど、is_string()Stringable が来たらどうなるんだろうな?勿論「objectなんだからstringじゃない」と言ってもらわないと困る気がするけども。
(お、こういう時にphptを見れば早いのでは・・?💡)

他言語だと == じゃね?

GolangやTSを例に引いて、「”無”なオブジェクトとの比較も == で行うしね、違和感とか覚えなくて良いはず。寧ろ自然じゃないかね?」というもの。

言語特性としてPHPは「色々な歴史がごちゃまぜになっている」という面があると思うので(良い悪いではなく!ただ、間違いなく特徴ではある)、使い手側が「負債を引きずり込みすぎない」というのは意識した方が良いよね〜って気持ちも個人的にあります。
(しかしそういう「PHPのゆるさ」みたいなところについて、RFCこの仕様はこの言語の暗黒時代の遺産を引き継いでいるって表現を見た時は笑ったw)

その観点から言うと、「他の言語やコミュニティの例に倣う」というのは時に効果的だな〜って思います。

is_null()

SQLだとさぁ・・・

SELECT * FROM table WHERE a = NULL;

って書きませんもんね・・! 全く違う世界の話を引きずらないで、と言われればそうなのですが、やっぱり「nullは中身が確定していない事を表す」はずなのに「イコールで比較とは果たして・・?」っていう気持ち悪さが頭をよぎります。

という人が他にも居てよかったw

(なおココでpy の if v is None: を挙げている人がいないのは、私のTLの偏りだと思います😃)*1

ちなみに、@d_horiyama_webさんにもらった「lintに従う」というのは凄く良い判断基準だよな〜と思っていて、まさにこういう「自転車置き場の色」は機械に覚えさせておいて判定させる、というのが良いですよね。*2

静的解析等で「=== nullを使え」という風になっていれば、それは凄く正しいというか「そうしましょう、なぜならそうすることが良いからです」みたいな話に落とし込めるので現実的に素敵な話。
ただ、これは独自に拡張していたルールなのかな・・?パッと探ってみて見つけられなかったです💦
Pslamでの挙動が一時的に不吉だった時期があったのかな?
=> is_null($value) not treated same as null === $value · Issue #2578 · vimeo/psalm · GitHub

実際問題、そういうのルール作って良い気がする。

f:id:o0h:20201214000919p:plain Psalm - a static analysis tool for PHP

f:id:o0h:20201214000941p:plain Playground | PHPStan

f:id:o0h:20201214001031p:plain Phan in Browser

て感じで、Psalm、PHPStan、Phanだと検出されたIssueなし。PhpStormでもソレらしきInspection見つからなかった・・かな?

「専用の表現あるなら使えば」派

「nullを示すもの」を判別するための「専用の書き方」があるのであれば、それを利用することで読み手の注意を引いたり「何をしたいのか」を判読するコストが下がるのでは?というもの。
(割と期間を一緒に働いていた元同僚なので、そういう温度感を互いに共有していたのかもな〜というメタな感想もいだきましたw)

それと、このツイートで情報として「PHPCSがsquizのrulesetで is_null() を禁止している」という情報が 💡
確かに、見てみるとForbiddenFunctions として設定されています。

github.com

なるほど、PHPCSの文脈で探ると何かあるかな・・?と思ったら、PHP-CS-FixerのIssueで興味深いものが出てきますね。

github.com

ここでベンチマークもはられていて、何だか条件によっては\is_null() の方が有利になる?という話も。

  • PHP 5.6: is_null($x) is 2.5x slower than null === $x with opcache disabled, and 2.2x slower with opcache enabled
  • PHP 7.0-7.2: the performance of both is the same, with and without opcache
  • PHP 7.3-7.4: is_null($x) is 1.2x faster than null === $x with opcache disabled, with opcache enabled there's no noticeable difference (but still, is_null() is 0.2% faster).

とはいえ、このIssue自体は「はっきりしたことが分かるまでは積極的にやる程でもないかなぁ」「生じたとしても微細な差分なので、今までのやり方を変えるほどかどうかは微妙?」というような温度感になっています。
(closeはされていないけども)

ということで

どっちかっていうと === null の方が分かりやすいのかもな〜って気がしてきました!
が、

  • まぁチームの好みに合わせて
  • lint入れてガッとやっちゃうのが健全じゃないかな?
  • でも多分、世の中の多くの人は大人なので「別にどっち来ても気にしないよ(表向きはね)」で済ませているんだと思います

というものです。

ちなみにアンケートも実施されているのですが、=== が優勢かな 😗

あと、他に「is_null() が (isset()と違い)関数なのこんがらがるね」という声もありました。
「is_hogehogeとishogehogeがあるの命名ばらばらで気持ち悪い」と思うか、仮に is_set() だったとしたら「is_hogehogeという命名の中で関数だったりそうじゃなかったりするの怖い」と思うか・・・?
って考えると、脳みそが痛くなりそうですね!とか思ってフフッてなりました。

なんにせよTwitterで色々と意見もらえたのが楽しい、こういうのあんまり仕事とか同チーム内でやると変な疲れ方しそうでもあるので・・!

*1:これ https://twitter.com/tadsan/status/1338075560979779584 は私も思うところ、というかinstanceof だけ文章っぽいのが違和感あるんだよなぁ。。

*2:今回の場合、技術的な観点での「こっちの方が良い」という意見が寄せられてますが、「readabilityに勝るもの無し」な差だと思うので「些細な問題」と言わせてもらっています。もちろん、意見表明(まして根拠の説明)をしてくれた人を軽んじるとかそういうアレじゃないです!!!