Upgrade to Pro — share decks privately, control downloads, hide ads and more …

責務を分離するための例外設計 - PHPカンファレンス 2024

責務を分離するための例外設計 - PHPカンファレンス 2024

PHPカンファレンス 2024での登壇資料です。
https://fortee.jp/phpcon-2024/proposal/ac2fa6f2-588f-4faa-a0e2-35e4d8d9f808

Takuma Kajikawa

December 21, 2024
Tweet

More Decks by Takuma Kajikawa

Other Decks in Programming

Transcript

  1. 実装や技術的な問題で起きる例外 ユーザーが通常のユースケースとして回避できないエラー。HTTPステータスでいう500系 起きる原 p バr p DBや外部APIなどの接続エラy p データの不整合 対処方)

    p 個別でcatchせずにFWや共通のハンドラーでcatchするd p プロトコルに対応したエラーコードを返すd p ユーザーに詳細なメッセージは見せないd p 「運営に連絡してください」「アクセスが集中しています。しばらくお待ち下さい」
  2. ユースケースで想定できる例外 クライアント側の問題。HTTPステータスでいう400系 起きる原s s ライブラリ特有の制約違t s アプリケーション特有の制約違t s (例) 予約する際の件数制限を超えていた

    対処方B s 例外をcatchする場合もある“ s catchしない場合はFWや共通のハンドラーでcatchする。(技術的な例外と同じ扱いT s プロトコルに対応したエラーコードや独自のエラーコードを返す“ s ユーザーに詳細なメッセージを返す“ s (例) 「予約可能な件数を超えています」
  3. Error 戻り値がintではなくfloatのときにPHPが投げる function int int : int return + (

    $a, $b) { $a $b; } ( , ); add add PHP_INT_MAX 1 TypeError 0除算のときにPHPが投げる function int int : int return / ( $a, $b) { $a $b; } ( , ); divide divide 10 0 DivisionByZeroError 直接throw文を書くことがないが、PHP内部で投げるオブジェクト。 「PHPの使い方を間違っている実装ミス」を表す。
  4. LogicException コードのロジックに問題があるときに投げる例外。 与えられた引数の条件を満たしてない場合など。 投げられた場合、ロジックの修正が必要だと判断できる。 RutimeException 実行時に予期せぬエラーが発生した場合に投げる例外。 実装が正しくても起きてしまう。 投げられた場合、一時的な障害やデータの不整合が発生し たと判断できる。 (

    $reservationId) { ($reservationId ) { ( ); } $reservation :: ($reservationId); ($reservation ) { ( ); } public function int : void if < throw new = if === throw new if === throw new cancelReservation find 1 LogicException Reservation null RuntimeException RuntimeException "IDは1以上である必要があります" "予約が見つかりませんでした。" "すでにキャンセルされています。" // DBからfetchする ($reservation->status 'canceled') { ( ); }
  5. カスタム例外 通常のユースケースにおいて想定される例外。 呼び出し側で必ず処理するかどうかを判断してほしい ( $reservationId) { $reservation :: ($reservationId); ($reservation

    ) { ( ); } public function int : void = if === throw new if === throw new cancelReservation find // DBからfetchする Reservation null CouldNotFoundReservation InvalidReservationStatus "予約が見つかりませんでした。" "すでにキャンセルされています。" ($reservation['status'] 'canceled') { ( ); }
  6. カスタム例外の設計 S Exceptionを継承す7 S 名前はどのようなケースで起きるかを表A S エラーのメッセージを内部で組み立てるようにす7 S factoryメソッドで生成する final

    class extends private function string parent:: public static function : return new self -> { ( $message, ) { ($message); } ( $reservationId ) { ( $reservationId ); } } CouldNotFindReservation __construct withId value Exception __construct ReservationId CouldNotFindReservation "予約が見つかりませんでした。ID: { ()}"
  7. ( $reservationId) { ($reservationId ) { ( ); } $reservation

    :: ($reservationId); ($reservation ) { ( ); } public function int : void if < throw new = if === throw new if === throw new cancelReservation find 1 LogicException Reservation null RuntimeException RuntimeException "IDは1以上である必要があります" "予約が見つかりませんでした。" "すでにキャンセルされています。" // DBからfetchする ($reservation->status 'canceled') { ( ); } 問題がある場合は早く中断する Î DBに問い合わせる前にIDのチェックしておµ Î 取得したオブジェクトを操作する前にチェックする
  8. 引数自身が例外を投げることでより早く中断できる Before After public function int     if <

    throw new ( $reservationId) { ($reservationId ) {     ( ); } ... cancelReservation 1 LogicException "IDは1以上である必要があります" class public function int if <= throw new -> = { ( $value) { ($value ) { ( ); } value $value; } ReservationId __construct 0 InvalidArgumentException $this "IDは1以上である必要があります。" public function ( $reservationId) { } cancelReservation ReservationId     // IDのチェックが不要になった W IDがロジック違反かどうかはID自身でチェックすI W IDのコンストラクタが呼ばれた段階で処理を中断できる
  9. IDが入力値やルーティングから与えられた場合、 クライアントエラー(想定内の例外)では? class public function int if <= throw new

    -> = { ( $value) { ($value ) { ( ); } value $value; } ReservationId __construct 0 InvalidArgumentException $this "IDは1以上である必要があります。" 入力値のチェックはバリデーターで行う。 バリデーション実装のミス(バグ)なので、LogicException(InvalidArgumentException)を投げる。
  10. 解決できない例外は呼び出し側に任せる S 呼び出し側で処理してほしい例外はPHPDocの @throws で仕様として宣言する /** * */ // 違反)

    { @throws public function if throw new MyException MyException () { ( (); } } myLogic try catch { (); } ( $e) { } myLogic MyException // 何かしらの処理 /** * */ @throws public function MyException myLogic () { } myLogicCaller 投げる 処理できる場合はcatchする 処理できない場合は更に呼び出し側に任せる