PHP初心者が仕事で躓いた4つの罠
こちらはピクシブ株式会社 Advent Calendar 2015の2日目の記事です。
こんにちは。Vimエンジニアの kana です。
さて、皆さんもご存知の通り、WebサービスのpixivにはPHPが使用されています:
PHPについては様々な噂を聞き及んでいた為、 これまでPHPとは関わらないように注意して過ごしてきましたが、 pixiv.netの開発ではPHPを避けて通ることは出来ません。
仕方なくPHPを使うことになる訳ですが、 実際に使ってみると……これが予想していた以上に様々な方向から毎日新鮮な驚きを届けてくれます。 今回は実際に遭遇したPHP初心者が躓くポイントを幾つか紹介しようと思います。
switch
の中で continue
したら switch
の直後に飛ぶ
大量のデータをループでぶん回して処理するのはよくある話です。 その中で特定の種類のデータについては処理をスキップすることもよくある話です。 例えば以下の様なコードを書いたとしましょう:
for ($i = 0; $i < 5; $i++) { switch ($i) { case 2: case 4: continue; } print "$i\n"; } //==> 0 // 1 // 2 // 3 // 4
あれれ……2と4はスキップされると思ったのに全部 print
されてしまってます。
不思議に思いつつ continue
のリファレンスを確認すると:
Note: In PHP the
switch
statement is considered a looping structure for the purposes ofcontinue
.
どうしてそうなった。
一応 continue 2;
で switch
の外のループを回すことは出来るのですが、
continue 2;
という字面が意味不明ですし、
PHPに詳しい先輩エンジニアから「PHPの switch
は危ない」とも言われたので、
これ以降 switch
を使うのは止めました。
クロージャーで使う外側の変数を明示しないと駄目
私はScheme大好き人間なので、データ列の処理は空気を吸う様に map
して filter
するのですが、
pixiv内部のソースコードには array_map
等を使わず愚直に foreach
で処理している箇所が散見されます:
$xs = [1 => 'aaa', 2 => 'bbb', 3 => 'ccc', 4 => 'ddd']; $ids = [1, 2, 3, 4]; $ys = []; foreach ($ids as $id) { $ys[] = $xs[$id]; } print implode(', ', $ys) . "\n"; //==> aaa, bbb, ccc, ddd
こういう箇所は「来た時よりも美しく」の精神で無意識のうちにリファクタリングしていまいます:
$xs = [1 => 'aaa', 2 => 'bbb', 3 => 'ccc', 4 => 'ddd']; $ids = [1, 2, 3, 4]; $ys = array_map(function ($id) {return $xs[$id];}, $ids); print implode(', ', $ys) . "\n"; //==> , , ,
おやおや、不思議な結果になってます。 困惑しつつAnonymous functionsのリファレンスを確認すると:
Closures may also inherit variables from the parent scope. Any such variables must be passed to the
use
language construct.
ふぁーーーーー何だそれーーーーーー!
なるほど、そりゃあ愚直に foreach
で皆コードを書いてる訳だ……
未初期化の変数を配列として使うと配列になる
一人で開発している時も勿論ですが、大人数で開発しているとなおさら万人が分かり易いようにコードを書くことになります。 ところが極稀に以下の様なコードに遭遇します。
$illust_id = 123; $options['size'] = 'small'; // <== ??? $options['include_metadata'] = true; $illust = fetch_illust($illust_id, $options);
う、うん? $options
はどこからも初期化されておらず、それをいきなり配列として使っています。
どういうことだと思いつつ変数の基本に関するリファレンスを確認すると:
It is not necessary to initialize variables in PHP however it is a very good practice. Uninitialized variables have a default value of their type depending on the context in which they are used - booleans default to FALSE, integers and floats default to zero, strings (e.g. used in
echo
) are set as an empty string and arrays become to an empty array.
お、おう……そうか……
この仕様を知っていれば便利に使える事があるかも知れませんが、 単なる書き忘れと区別が付かないことが殆どなので、こういう箇所は見つけ次第撲滅してます。
ユーザー定義関数の名前の大文字小文字は区別されない
pixivのコードベースは巨大なので、 気付かないうちにどこからも使われなくなった関数がどうしても増えていきがちです。 という訳でここ最近のpixivでは「未使用関数削除祭り」と称してコードの整理を行っています。 機械的に抽出した未使用関数の数々に対してざっくり担当エンジニアを決め、 各自半日程度の時間を取って本当に不要な関数かどうかを確認・削除しています。
抽出対象はgrepして定義行しか引っかからなかった関数なので、基本的にはただ定義を削除するだけで済む作業です。 とはいえ万が一の可能性も無くはないので、念の為に関連するコードも確認します。 私も自分の担当分について一通り確認した上であれこれ削除していきました。
ところがある関数を削除したものを本番環境へデプロイした途端に毎秒のペースでエラーが発生するようになりました。 一旦revertしてから原因の調査を始めるのですが、 「grepしても引っかからない関数なのに削除したらエラーが出る」 ということは実際にはどこか呼んでいる箇所が存在するということになります。
まさか……と思いつつユーザー定義関数のリファレンスを確認すると:
Note: Function names are case-insensitive, though it is usually good form to call functions as they appear in their declaration.
ウッ。
恐る恐る実際のソースコードを確認すると……
$ grep -i getSomethingByUID .../foo.php: function getSomethingByUID(...) .../bar.php: $something = getSomethingByUId(...);
なんでそこ d
小文字にした!
(そっと修正してデプロイし直しておきました)
まとめ
上記の4点以外にも色々とPHPで躓いた話はあるはずなのですが、毎日驚きの連続で記憶から飛んでしまいました。PHP凄い。
次回は saturday06 さんによる凄い趣味の話です。お楽しみに!