PHP8におけるエラーハンドリングについて考えてみた

こんにちは、エンジニアの鵜川です。

Web業界に入って役半年以上が経ちました。そんな私が業務を進めていく中で理解するまでに時間がかかった概念が ”例外” なんですよね…

そもそもプログラム上における例外(Exception)とは最近話題の ChatGPT によると

実行時に予期しない問題が発生した場合に、プログラムの正常な流れを中断させて、その問題を示すエラーメッセージや、エラー処理のための特別なコードを実行するための手段

だそうです。つまりシステムが稼働している途中で例外が発生した場合

  • プログラムを中断させ例外をキャッチしログにエラーの内容(開発者向け)を吐き出す
  • プログラムを中断させ例外をキャッチし 適切な処理 または 再試行処理 を行う

上記2点のどちらかの対応が必要になってくるのかなと私は考えています。このどちらかの対応を行なった後にエラーの有無をユーザーに対して通知する必要性があります。

ここで経験が浅い私からすると発生した 例外 によって どんな対応 を取るのが適切なのかを判断するのが難しかったんですよね…

まあこの問題に関しては 適切なエラーハンドリングの設計 を行うことによって解消することができます。とは言ってもある程度は開発者の判断によってエラーハンドリングを行わなければいけません。

そこで私なりに PHP で発生する例外についてのエラーハンドリング方法を種類別にまとめてみました。

PHP例外構造

上記のクラス図を見てもわかる通り PHP の例外クラスは 4種類に分別できることがわかります。4種類全てが Interface として Throwable が継承されており Exception または Error のどちらかを継承して分別されていることが一目瞭然ですよね。

try {
       //例外が発生するかもしれない処理
} catch (\Exception $e) {
     //エラーハンドリング
}

上記のコードでは Exception を継承していない Error 関連クラスはキャッチしないようになっています。Laravel などのフレームワークでは自作の Exception クラスを作成することも可能ですよね。

Error

Error は PHP 7.0 から導入された概念です。言語上、基本的な何かが間違っているという問題をプログラマーに教えるためにあります。 例外自体を捕捉する必要性はないと言えます。プログラム自体の修正が必要になってきます。

例えば 0 で除算しようとした場合に DivisionByZeroError が発生します。

echo 99 / 0; //Error

Exception

Exception から派生した例外のうち、LogicException / RuntimeException でないものは、それらとは明確に区別されます。この種の例外が発生した場合はかならずエラーハンドリングしなければなりません。

例えば配列を json_encode() で json形式に変換する場合、JsonExceptionが発生する可能性があります。このケースでは例外をキャッチしログに吐き出すのが良いでしょう。

try {
     json_encode(対象となる配列, JSON_UNESCAPED_UNICODE);
} catch (\Exception $e) {
    //エラー内容をログに吐き出す処理
}

LogicException

LogicException は、ライブラリ作者にとっての Error です。PHP コードを書くプログラマーは自分で Error を発生させることができないので、Exception を使うことになります。Error が PHP 言語上の異常なら、LogicException は API の使い方における基本的な間違いを示す異常 ですので 例外を捕捉する必要性がありません。

例えば、配列の範囲外のインデックスにアクセスしようとする場合、配列インデックスが存在しないため、LogicException がスローされます。つまり 一般的に LogicException が発生する場合、コード上に品質的な問題がある ということになるんですよね。

$signals = [ '赤', '黄', '青' ];
echo $signals[3]  //LogicException

RuntimeException

RuntimeException は、いくらロジックで努力しても、どうしても発生してしまう、運用/インフラレベルの障害の発生を表します。RuntimeException の捕捉は任意とされています。特別な理由がないかぎり、RuntimeException はなるべく捕捉しないことをオススメします。

捕捉したところでまともなエラーハンドリング処理を実行できる機会が少ないからです。しかし、複数のテーブルにデータを保存する処理では RuntimeException (PDOException) を捕捉し、エラーハンドリングの中で適切なロールバック処理を実行することが必要になってきたりもします。

まとめ

基本的に捕捉する例外としては Exception のみで良いと私は考えています。PhpStormでもデフォルトで、コードフォーマッタが RuntimeException と LogicException を無視するように設定されています。例外を捕捉しすぎても以下のような問題が出てきます。

  • 例外処理のオーバーヘッド:例外処理は、通常の処理よりも多くのコストを伴います。
  • フロー制御の複雑化:例外処理を過剰に使用すると、プログラムのフロー制御が複雑化する可能性があります。
  • 要求される処理の増加:例外処理を過剰に使用すると、プログラムに要求される処理が増加する可能性があります。

これらの理由から、例外処理は必要最小限にとどめることが望ましいです。

Related article

おすすめ関連記事