読者です 読者をやめる 読者になる 読者になる

肉とビールとパンケーキ by @sotarok

少し大人になった「肉とご飯と甘いもの」

PHP プログラマが "@" を使うべきでない 5 つの理由

PHP

#釣りっぽいタイトルですが大まじめです

で、 @ (エラー制御演算子といいます!)はねーよ的な話をしましたが、著者の方から、「@に対して批判的になる理由が記載されていない」とのメールをいただきました。確かにその通りでした。実は理由を下書きのときには書いたのですが、長くなってしまったので削ってポストしたのですが、かえってわかりづらくなってしまいましたね.すみません。

ということで、PHPプログラマが、エラー制御演算子「@」使うべきでない 5 つの理由を述べます.

始める前に、本質的なところ

色々理由はつけようと、やっぱり前回述べた、

終的に$qに入るものが同じであることと、コードとして同じ意味であるかは、別じゃないでしょうか。

が一番本質的な話で、それ以上の話ではありません。

つまり、発生する可能性があるとわかっているエラーを表示させないことと、エラーがあるかどうかをチェックして適切に処理をするのかは、意味が全然違うという意味です。

1. エラーが発生する理由がわからない

わざとエラーを表示させないようにしているので当たり前ですが、エラーが表示されません。

例えば、以下の例を見ましょう。

<?php
$_POST['hoge'] = 1;
$a = $_POST['hoge'];
$b = $_POST['fuga'];
var_dump($a);
var_dump($b);

これを実行すると、

Notice: Undefined index:  fuga in /Users/sotarok/tmp/hoge.php on line 4
int(1)
NULL

となりますね。
これは、$_POSTに、hogeというインデックスはあるけど、fugaというインデックスはないので、$a、$bはそれぞれ、1($_POSTのhoge)、null(fugaはセットされていないので)となります。
「fuga という配列のインデックスは無いよ」というNoticeを出してくれます。

POSTに、hogeもfugaも入ってくるか入ってこないかわからないので、Noticeが表示されては鬱陶しいので、@を着けてしまいましょう。

<?php
$_POST['hoge'] = 1;
$a = @$_POST['hoge'];
$b = @$_POST['fuga'];
var_dump($a);
var_dump($b);
int(1)
NULL

一見、すっきりしましたし、やりたいことも実現できました。


では、以下の例ではどうでしょう?

<?php
$_POST['hoge'] = 1;
$a = @$_POTS['hoge'];
$b = @$_POTS['fuga'];
var_dump($a);
var_dump($b);
NULL
NULL

おやや!hogeには1をセットしているのに、NULLになってしまっています!!


”@”を外してみましょう。すると、実行結果は、以下のようになります。

Notice: Undefined variable: _POTS in /Users/sotarok/tmp/hoge.php on line 3
Notice: Undefined variable: _POTS in /Users/sotarok/tmp/hoge.php on line 4
NULL
NULL

ああ、なんだ、 POST を間違って POTS と打ってしまっていたのか、とタイプミスに気づくことになります。


こんな小さなプログラムで、これだけを見ると、「こんなミスはしない」「気づくだろ」などと思ってしまうかもしれませんが、これは非常に危険なことです。
@をつけたその文の、どこでなぜエラーが発生しているのかが一切わからないからです。

このように、本来気づけるはずだったくだらないミスを、エラーが出ないがためにみつけられず、1日を潰しすことになります

2. @をつけるクセがつくと適切に値のチェックをする方法が身に付かない

極端な言い方かもしれませんが、
「なんかエラーが出てるなあ。まあでも動くし、@ つけときゃいいか」
という思考が生まれるのが、なによりも危険だと思います。

$_POST で null or 値 をとるのに @ をつけると簡単だ、と覚えてしまったプログラマは、「動くけどなんかエラーがでるなあ」という状況で次に何をしてしまうのか、想像するだけで鳥肌がたちます!

というやけに和訳風のセリフはともかく、まあやっぱりこれが一番怖いな、と思います。なにかエラーが起きたときに、そのエラーの意味を知り、なぜそれが起きるのか、ではどうすれば起きなかったのか、それをしっかりと学ぶべきなのではないでしょうか。

とくに、件の書籍に関しては、「初心者」がターゲットとなっているのです。わかったうえでやむを得ないシチュエーションで @ をつけてよしなに、ということではなく、最初から、 「null or 値をとるには、多少遅いけどこっちの方が簡単だから、@つけちゃおう」という風にPHPを身につけてしまうと、当然、他の部分でもそれは実践されてしまうと思います。

3. エラーのハンドリングによっては意味をなさない

PHPには、PHPが吐くエラーを捕まえて独自のエラー処理をすることができます。*1


では、以下の例を見てみましょう。

<?php
set_error_handler("my_error");
$_POST['hoge'] = 1;
$a = $_POST['hoge'];
$b = $_POST['fuga'];
var_dump($a);
var_dump($b);

function my_error($errno, $errstr, $errfile, $errline)
{
    // 本当はここではエラーの種類によって処理をわけたり、ファイルに書き出したりなど
    echo "My PHP Error: $errno, $errstr, in $errfile, on $errline\n";
}

実行すると、

My PHP Error: 8, Undefined index:  fuga, in /Users/sotarok/tmp/hoge.php, on 5
int(1)
NULL

このように、カスタムされたエラーが出力されます。

では、また鬱陶しいので、 @ をつけましょう。

<?php
set_error_handler("my_error");
$_POST['hoge'] = 1;
$a = @$_POST['hoge'];
$b = @$_POST['fuga'];
var_dump($a);
var_dump($b);

function my_error($errno, $errstr, $errfile, $errline)
{
    // 本当はここではエラーの種類によって処理をわけたり、ファイルに書き出したりなど
    echo "My PHP Error: $errno, $errstr, in $errfile, on $errline\n";
}
My PHP Error: 8, Undefined index:  fuga, in /Users/sotarok/tmp/hoge.php, on 5
int(1)
NULL

またエラーが出てしまいました。@ を着けているはずなのにどうしてでしょうか。

PHPのマニュアルにはきちんとこの点が触れられています。

PHP の標準のエラーハンドラは完全にバイパスされることに注意してください。 error_reporting() の設定にかかわらず、どのような場合でも ユーザが設定したエラーハンドラがコールされます。ただし、この場合でも ハンドラで error_reporting() のカレントの値を読み、 それにあわせて適切に動作させることは可能です。エラーを発生した命令の前に @ エラー制御演算子 が付加されている場合、この値は 0 となることには注意しましょう

PHP: set_error_handler - Manual

そう、error_repoting や @ の有無に関わらず、エラーハンドラは呼ばれるのです。
もちろん、「error_reporting() の値をチェックして適切に処理するエラーハンドラを定義すればいいだろう」という主張はあるとは思いますが、”@”をつけたコードは、ハンドラの違いによってエラーの制御が適切にできるかできないかわからないコードとなってしまうのです。

4. 遅い

こんなのは、上記3つから見たら正直おまけ程度ですが、速度は遅いです。一時期この話題が流行ったこともありますのでチェックしてみると良いのではないでしょうか。この点は、書籍にも書いてありました。*2

詳しいことは、ソース読めってことで、僕も少し読みました.気になる方は、

あたりを見ると、 T_ISSET と @ の違いがわかるのではないでしょうか.


以下の記事も良い記事でした。非常にわかりやすく書いてありました。

5. きもちわるいから

だってエラーが本来出るはずなのにエラーを出さなくするんですよ
そんなのきもちわるいじゃないですか!!*3


#ごめんなさい。
#5つってタイトルが語呂がよかったから5つくらいかきたかったけどもう眠いです。


その他、様々な人の意見

自分の認識がもしかして間違ってるんじゃないか、と思って、少し周りの人にも意見をきいてみました。どれも、上記1-4(5)に当てはまるような意見ではないでしょうか?

遅い。あと本来見つけられるべきエラーが見つけられなくなるよね
-- ふじもと氏

むかし、Net_UserAgent_MobileがWarningを吐くから @ new などとしていたら、
ライブラリがインストールされていないサーバに設置したときに1日はまった
-- 個々一番氏

@ とか使わない
しかし、PHP的といえばPHP的だw
-- riaf氏

結論

  • エラーはきちんとチェックする
    • 値が入っているのかいないのか、入っている値がおかしいのか正常なのか
  • 配列に値がセットされてるか、変数がセットされているかは、issetで
  • @を使うのはやむを得ない場面や、仕方がない場面(このPHP関数、関数内部でこういうWarningだしちゃうんだよ!的な)など、最低限に限るべし
  • 5つまとまらなくてごめんなさい。でも、@ 良くないなあ、という理由はだいたい言った

いや、 @ 使った方が良い、という理由をお持ちの方がいたら、それはそれで教えていただきたいな、と思います。
(しかし、なんとなく、PHP 5.3 の $a = $hoge?: null; には @ を着けたくなっちゃいますよねえー。なんなんですかね、この仕様。。!超中途半端ですね。)


PHP 逆引きレシピ (PROGRAMMER'S RECiPE)

PHP 逆引きレシピ (PROGRAMMER'S RECiPE)


*4

*1:多くのフレームワークでは、set_error_handlerを独自にエラーハンドリングをし、その結果をファイルに吐き出したり、メールでアラートを飛ばしたりなどする機能を持っています。ここでは多くは触れませんが

*2:なぜ遅いかは書いてありませんが

*3:PHPがきもちわるいんですか、そうですか

*4:special thanks: この記事を書くにあたって、anatoo, riaf, ここいちさん、ふじもとさんなどにご意見を伺わせていただきました。ありがとうございます。