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

レガシーシステムから学ぶ エラー処理との付き合い方

uiui
January 29, 2025
82

 レガシーシステムから学ぶ エラー処理との付き合い方

レガシーシステムで起きている問題を踏まえて、どうエラー処理と付き合うべきなのか、例外処理はどう使うべきなのかを探求するお話

uiui

January 29, 2025
Tweet

Transcript

  1. PHP4時代のエラー処理 
 ・例外処理なし ・E_ERROR, E_WARNING, E_NOTICE 等のエラーが発生 し、警告が出力されたり、実行を中断したりする 制御する方法①error_reporting() で検知するエラーレベルを設定する

    (出力する かどうかのみ。0に設定するとE_ERRORの時検知しないで止まってしまい、真っ 白になってしまう) display_errorsは出力を設定 制御する方法②set_error_handlerでエラー発生時の処理を定義する エラーレベルごとに処理を設定する(ログとか) 初期PHPはどうやってエラー処理をしていたのか?
  2. file_get_contentsの仕様を見てみよう 
 (PHP 4 >= 4.3.0, PHP 5, PHP 7,

    PHP 8) file_get_contents — ファイルの内容を全て文字列に読み込む 読み込んだデータを返します。 失敗した場合false を返します。 戻り値 また多くの場合 E_WARNING レベルのエラーが発生する
  3. PDO::queryは? 
 (PHP 5 >= 5.1.0, PHP 7, PHP 8,

    PECL pdo >= 0.2.0) PDO::query — プレースホルダを指定せずに、SQL ステートメントを 準備して実行する PDOStatement オブジェクトを返します。 失敗した場合に false を返します 戻り値 *例外を投げるモードに変更可能
  4. public function uploadIconImage($request_img) { /** @var ImageUploadService $this->uploader **/ $info

    = $this->uploader->getInfo($request_img); if ($info['result'] !== false) { if ($info["size"] < ICON_SIZE) { if ($info["type"] == "png") { $result = $uploader->uploadImage($request_img, ICON_PATH); if ($result['result']) { return ICON_PATH; } else { return false; } } else { return false; } } else { return false; } } else { return false; } } うわぁ、分岐が … 早期リターンで 書いてみよう
  5. public function getInfo($img) { if ($err) { return false; }

    ... return $info; } function UploadImage($img, $file_name = null) { if (!$img['file']['tmp_name']) { return false; } ... if ($err) { return false; } … } public function uploadIconImage($request_img) { /** @var ImageUploadService $this->uploader **/ $info = $uploader->getInfo($request_img); if ($info['result'] === false) { return false; } if ($info["size"] > ICON_SIZE) { return false; } if ($info["type"] != "png") { return false; } $result = $uploader->uploadImage($request_img, ICON_PATH); if (!$result['result']) { return false; } return ICON_PATH; } ImageUploadService IconImageService 🫠下層のどこでエラーが起きたかわからない … 🫠エラーに対するコード量が多い 🫠戻り値の型が mixed
  6. public function uploadIconImage($request_img): array { /** @var ImageUploadService $this->uploader **/

    $info = $this->uploader->getInfo($request_img); if ($info[“result”] === false) { return ["result" => false, "err_msg" => "エラー..."]; } if ($info["size"] != ICON_SIZE) { return ["result" => false, "err_msg" => "サイズが..."]; } if ($info["type"] != "png") { return ["result" => false, "err_msg" => "png形式..."]; } $result = $uploader->uploadImage($request_img, ICON_PATH); if ($result['result']) { return ["result" => true, "path" => ICON_PATH]; } else { return [ "result" => false, "err_msg" => $result[“err-mes”] ]; } } IconImageService public function getInfo($img): array { if ($err) { return ["result" => false,"err-mes" => "エラー..."]; } ... } function UploadImage($img, $file_name = null) { if (!$img['file']['tmp_name']) { return [ 'result' => false, 'err-mes' => 'ファイルを指定してください' ]; } ... if ($err) { return ["result" => false, "err-mes" => $err]; } … } ImageUploadService 🫠エラーをバケツリレーしなきゃいけない 🫠開発者によってキー名にばらつきが … 連想配列でエラーメッセージも一緒に返して、どこでエラーが起きたか一応わかるバージョン
  7. //システム的なエラーだったらログ残したほうが … //でもバリデーションエラーでログ書かれたらめんどくさい $res = uploadIconImage($_FILES["icon_img"]); if (!$res["result"]) { $error_message_to_user

    = $res["err_msg"]; } //失敗したときの処理書いてないような …? uploadIconImage($_FILES["icon_img"]); 🫠無視されやすい 🫠なんの種類のエラーなのかわからない (開発中もデバッグばかりする )
  8. //システム的なエラーだったらログ残したほうが … //でもバリデーションエラーでログ書かれたらめんどくさい $res = uploadIconImage($_FILES["icon_img"]); if (!$res["result"]) { $error_message_to_user

    = $res["err_msg"]; } //失敗したときの処理書いてないような …? uploadIconImage($_FILES["icon_img"]); 🫠無視されやすい なんの種類のエラーなのかわからない これは特に厄介!!
  9. public function uploadIconImage($request_img): string { /** @var ImageUploadService $this->uploader **/

    $info = $this->uploader->getInfo($request_img); if ($info["size"] != ICON_SIZE) { throw new IconImageInvalid("サイズが…"); } if ($info["type"] != "png") { throw new IconImageInvalid("png形式で.."); } $uploader->uploadImage($request_img, ICON_PATH); return ICON_PATH; } IconImageService 😎戻り値の型を一貫して保てる mixedじゃなく、エラーを返す表現ができる! 正常ならstring型が、 異常時はエラーがスローされる
  10. public function uploadIconImage($request_img): array { /** @var ImageUploadService $this->uploader **/

    $info = $this->uploader->getInfo($request_img); if ($info["size"] != ICON_SIZE) { throw new IconImageInvalid("サイズが…"); } if ($info["type"] != "png") { throw new IconImageInvalid("png形式で.."); } $uploader->uploadImage($request_img, ICON_PATH); return ICON_PATH; } IconImageService public function getInfo($img): array { if ($err) { throw new ImageDataRetrievalException("取得できず"); } ... } public function UploadImage($img, $file_name = null) { if (!$IMG['file']['tmp_name']) { throw new FailedUploadException("ファイルが..."); } … if ($err) { throw new FailedUploadedException("なにかのエラー "); } … } ImageUploadService 😎上位層でtry-catch を書くだけ (バケツリレーなし ) 😎コード量が減った 大域脱出 try { $service->uploadIconImage($file); } catch (Exception $e) { showError($e->getMessage()); }
  11. Exception自体が便利なんだよな 
 Exception::getMessage — 例外メッセージを取得する Exception::getCode — 例外コードを取得する Exception::getFile —

    例外が作られたファイルを取得する Exception::getTrace — スタックトレースを取得する (プログラム実行時の関数呼び出し履歴) 😎エラー表現は Exceptionで統一 欲しい機能は備わっている 😎どこでエラーが起きたのかスタックトレースでわかる
  12. 独自例外クラス 
 class FailedUploadedException extends RuntimeException { } class IconImageInvalidException

    extends ValidationException { } try { $service->uploadIconImage($file); } catch (ValidationException $e) { $error_message_to_user = $e->getMessage(); } 外部環境要因なのでRuntimeExceptionをベースに ユーザの入力の問題なのでバリデーションエラー バリデーションエラー以外は キャッチしない 😎エラーの種類別に 処理を分けられる
  13. PHPDocで例外出るメソッドだよと伝える 
 /** * @throws ValidationException */ public function uploadIconImage($request_img):

    string { … $service->uploadIconImage($file); 🧐戻り値で処理してた時よりは無視されなさそう? 😃もしエラーが起きても落ちてくれるのでまあ良さそう PHPStormで波線引かれる
  14. プログラムの流れを追うのが難しくなる 
 例外が投げられると、関数の途中の思いもしないところから、唐突にその呼び出し元へと処理が 戻ってしまいます。 このことは、メンテナンス性の低下や、デバッグの難しさにつながります。 エラーが特別な制御構造を使用する場合、エラー処理によって、エラーを処理するプログラムの 制御フローが歪んでしまいます。 Java のようなスタイルのtry-catch-finally ブロックは、複

    雑に相互作用する複数の重複する制御フローを織り交ぜます。 By Google C++ スタイルガイド https://ttsuki.github.io/styleguide/cppguide.ja.html#Exceptions Google で学ぶ: ソフトウェア エンジニアリングに役立つ言語設計 https://go.dev/talks/2012/splash.article#TOC_16. 🧐複雑な構成・変な使い方するとプログラムを追うのがしんどそう
  15. 適切でない例外の使い方をしたときによりデバッグ・運用が大変になりそう① 
 try { validateInput($input); saveToDatabase($input); sendConfirmationEmail($input); } catch (Exception

    $e) { Log::logput("エラーが発生しました : " . $e->getMessage()); } 🫠抽象的すぎて何が起きたかわからない、、 メッセージだけ残されましても ....
  16. 適切でない例外の使い方をしたときによりデバッグ・運用が大変になりそう② 
 try { // 何らかの処理 } catch (Exception $e)

    { // スタックトレースなしで再スロー throw new Exception("エラーが発生しました"); //throw new Exception("エラーが発生しました: " . $e->getMessage(), 0, $e); } 🫠スタックトレースが途中で消えて流れを追えなくなる 階層が深い時スタックトレースないと厳しいよね …
  17. 適切でない例外の使い方をしたときによりデバッグ・運用が大変になりそう③ 
 try { $service->purchase($item); } catch (Throwable $e) {

    // 実装ミスなども含めて全ての例外をキャッチしてしまう echo "購入に失敗しました "; //本来は業務エラーだけキャッチする予定だった } 🫠意図しないエラーもキャッチしちゃう
  18. 例外的なものとそうでないものってなんだろう 
 エラーはどう分類できるだろうか 
 ・システムエラー 業務上想定しない技術的な問題のエラー ・ネットワークエラー ・データベース接続エラー ・バグ 等々

    ・業務エラー 業務上想定されている範囲内の異常事態で、誤入力・誤操作などが原因 ・入力のバリデーション ・ポイント不足ですよ とか ・回数上限に到達してますよ とか
  19. ・システムエラー 業務上想定しない技術的な問題のエラー ・ネットワークエラー ・データベース接続エラー ・バグ 等々 ・業務エラー 業務上想定されている範囲内の異常事態で、誤入力・誤操作などが原因 ・入力のバリデーション ・ポイント不足ですよ とか

    ・回数上限に到達してますよ とか ログに残し、管理者に連絡 ユーザに失敗理由を表示 管理者には連絡しない! 例外的なものとそうでないものってなんだろう 
 エラーはどう分類できるだろうか 

  20. 例外処理のメリット・デメリット・議題まとめ 
 😎戻り値の型を一貫して保てる 😎バケツリレーなしでコード量が減る 😎エラー表現の統一 😎スタックトレース 😎エラー種類別に処理を書ける 😎(catch しなければ )その場で落とせる

    🫠例外安全か気にする必要がある 🫠プログラムの流れを追う時のコスト増 🫠例外の扱い方とガイドの共有コスト増 (🫠エラー処理が他人任せ ) *↑が守られないと、デメリットの方が上回る メリット デメリット 🧐業務エラーは例外処理で対応すべきなのか 
 議題
  21. 参考文献・資料 
 Google C++ スタイルガイド https://ttsuki.github.io/styleguide/cppguide.ja.html#Exceptions Go 言語の貢献者 Dave Cheneyのブログ

    https://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right Google で学ぶ: ソフトウェア エンジニアリングに役立つ言語設計 https://go.dev/talks/2012/splash.article#TOC_16. PHP7で堅牢なコードを書く https://speakerdeck.com/twada/php-conference-2016 PHPでthrowしない例外ハンドリング https://speakerdeck.com/tanden/phpdethrowsinaili-wai-handoringu ちょっと広く例外を学んでみた https://qiita.com/TairaNozawa/items/8788c4b20c60046ee80c ソフトウェアデザイン 2024年9月号