はじめに
プログラムを組んでいく中で、気を付けなければいけないコーディングは多々あります。
知識としては蓄えていても実践だと見落としてしまうものです。
初心者時代のエピソードを交えつつご紹介します!
目次
その処理はすでに関数があるかもしれない
設計書 「ユーザー名が英数字で構成されているか、それ以外かで処理を分ける」
私「preg_match関数で英数字かどうか判定して処理を分けよう」
※【英数字 正規表現】で検索しました。
$pattern = '/^[a-zA-Z0-9]+$/';
if (preg_match($pattern,$user_name)) {
// 英数字の処理
} else {
// 英数字以外の処理
}
実は英数字かどうかを判定する関数が存在します
【ctype_alnum】
上記のコードは以下のように書き換えることができます。
if (ctype_alnum($user_name)) {
// 英数字の処理
} else {
// 英数字以外の処理
}
ctype_alnumを使うメリットとしてはレビューがしやすいという点があります。
前者のコードだと、レビュワーは正規表現が正しいものか注力して見なければいけませんが、
後者はPHPマニュアルに仕様が記載しているため可読性に優れます。
今回の場合は【PHP 英数字 判定 関数】のように検索をすれば気付けたかもしれません。
関数が存在するかも意識して検索してみてください。
小数点を扱う際は丸め誤差に注意
問い合わせ 「なんか合計がおかしいので調べてください!」
私「こ、これは!」
$amount = 100;
$rate = floor((0.1 + 0.7) * 10);
$totalAmount = $amount * $rate; // 期待値 800
echo $totalAmount; // 700
大分脚色したコードです。期待している値が「800」なのですが上記では「700」が返却されていました。
有名な小数点での演算誤差です。
※PHPだけでなく他の言語でも発生します。
PHPのマニュアルにも以下のように注釈が記述されています。
十進数では正確な小数で表せる有理数、たとえば 0.1 や 0.7 は、 二進数の浮動小数点数としては正確に表現できません。 これは、仮数部をいくら大きくしても同じです。
この場合ですと、0.1 + 0.7をfloor関数で切り捨てると「0.7」が返却されてしまいます。
プログラム内部ではどのようなことが起こっているのか見てみましょう。
echo number_format(0.1, 30); // 0.100000000000000005551115123126
echo number_format(0.7, 30); // 0.699999999999999955591079014994
echo number_format(0.1 + 0.7, 30); // 0.799999999999999933386618522491
echo number_format((0.1 + 0.7) * 10, 30); // 7.999999999999999111821580299875
0.1と0.7の和が「0.7999...」となっていることが確認できます。
そこに10の積で「7.999...」になったところにfloorで「小数点以下を切り捨てた整数値」の「7」が返却されることになります。
【floor】
実際に遭遇してみるとインパクトが大きかったです。
任意精度数学関数または gmp 関数を代わりに使用してください。
計算ロジックは重要な機能の根幹を担う事が多いので、
小数点を扱う際は気を付けましょう。
条件分岐の判定に使う関数の返却値に注意
皆さんもIF文はよく利用されるかと思います。
配列操作の関数を用いて処理する際に起こったバグです。
設計書 「配列の中の'apple'にだけ特殊な処理を行う」
私「配列の中身を検索するarray_searchって関数があるぞ!」
・・・
・・
・
私「あれ?処理が正常に動かないパターンがあるぞ」
$arr = ['apple','orange','banana'];
if(array_search('apple',$arr)){
// appleの時の処理
}else{
// その他の処理
}
上記のコードは「'apple'が最初」だった場合、ELSEの処理に入ってしまいます。
【array_search】
array_searchは「指定した値を配列で検索し、見つかった場合に対応する最初のキーを返す」のです。
つまり、'apple'が最初の場合array_searchは添え字である数字の「0」を返却します。
この場合、「0」は「FALSE」として扱われるのでELSEの処理に入ってしまっていました。
警告
この関数は論理値 false を返す可能性がありますが、false として評価される値を返す可能性もあります。
一致しなかった場合、array_searchはFALSEを返却しますので比較演算子で対応します。
$arr = ['apple','orange','banana'];
if(array_search('apple',$arr) !== false){
// appleの時の処理
}else{
// その他の処理
}
使う関数がどのような型を返却するかは注意しなくてはいけません。
幸いなことに単体テストで不具合を見つけました。
閾値のテストの重要性を再認識させられた出来事でもありました。
現行のPHPバージョンで使える関数でも将来的に削除される関数は使わない
既存処理と似たロジックを組む際に、その既存ロジックを参考に新規ロジックを組むことは多いと思います。
参考にしたコードで、PHPのバージョンアップを想定した時に、「削除」または「非推奨」の関数をそのまま踏襲することは望ましくありません。
有名なところですとereg系でしょうか。
警告
この関数は PHP 5.3.0 で 非推奨 となり、 PHP 7.0.0 で 削除 されました。
代替する関数としてpreg_match()を使うように記述されています。
上記で「非推奨」とされているものも、
将来的にPHPのバージョンが上がったタイミングで削除される可能性が高いため使わないようにしましょう。
また、関数自体の仕様変更も注意します。
例えば、汎用的なcount()関数はバージョンで以下のように変更されています。
バージョン | 説明 |
---|---|
8.0.0 | パラメータに不正な型を渡した場合に、 TypeError をスローするようになりました。 |
7.2.0 | パラメータに不正な型を渡した場合に、 警告を発生させるようになりました。 |
ここのパラメータの不正な型というのは「NULL」も含まれます。
つまり、PHP 7.2以前はcount(NULL);
は「0」を返却していましたが、
7.2以降はWarning、8.0以降はエラーが返却されるようになります。
この場合、PHP 7.2の時点でcount関数の警告に対応するようにすれば、
PHP 8.0でも問題は起こらないはずです。
システムのPHPバージョンアップに備えて、更新履歴等の情報にはアンテナを張るように意識しましょう!
おわりに
記事を書いている途中でも異なった文字コードでサーバーにアップしそうになった事や、Linuxコマンドでrmとmvコマンドを間違いそうになった事など書ききれないほどフラッシュバックしました。
PHPでは基礎中の基礎みたいな内容でしたが、いかがでしたでしょうか?
エピソードも交えるとより気を付けやすくなるのではないのかと思います。
今回のように同僚や先輩に気を付けていることを聞いてみるのもいいかもしれません!
参考資料
その他参考になる資料をまとめさせていただきました。
・PHP7の例外(Exception)の関係、正しく例外を使いましょう。(最終閲覧日:2022年9月4日)
・PHP isset, empty, is_null の違い早見表(最終閲覧日:2022年9月4日)
・PHPの落とし穴(最終閲覧日:2022年9月4日)