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

例外処理を理解して、設計段階からエラーを見つけやすく、起こりにくく

 例外処理を理解して、設計段階からエラーを見つけやすく、起こりにくく

エラーが発生したとき、ログを追ったりデバッグを繰り返したりするのは大切な作業ですよね。でも、それだけだと「エラーが起きた後の対処」に留まってしまいます。もっと良い方法があるとしたら?

設計の段階から、エラーが「見つけやすい」仕組みや「そもそも起きにくい」コードの書き方を取り入れることで、システムの信頼性がグッと上がり、あとから困ることも減ります。

このセッションでは、例外の基本的な役割や考え方から始めて、PHPやJavaのように例外を持つ言語と、RustやGoのように例外を使わない言語のエラーハンドリングを比較。それぞれの特徴を活かした設計方法をお話します。難しい話だけでなく、「こうすれば実務で役立つ!」という具体例も紹介しますので、チームのディスカッションにもぜひ活用してください!

PHPカンファレンス福岡2025
https://fortee.jp/phpcon-fukuoka-2025/proposal/a26758d0-0d75-4234-a21b-cbb4ff64763b

Avatar for Takuma Kajikawa

Takuma Kajikawa

November 08, 2025
Tweet

More Decks by Takuma Kajikawa

Other Decks in Programming

Transcript

  1. 梶川 琢馬 / 𝕏 @kajitack 株式会社 TechBowl の VPoT TechTrain

    の開発、メンターをやってます。 初PHPカンファレンス福岡! 4/63
  2. 例: 注文処理 /** * @param $productId 商品ID * @param $quantity

    数量 */ function createOrder(int $productId, int $quantity): Order { // 永続化 Order::create(['productId' => $productId, 'quantity' => $quantity]); } 6/63
  3. エラー対応を注文処理にそのまま記述した function createOrder(int $productId, int $quantity): Order { // 妥当性チェック

    if (!productExists($productId)) { // エラー対応 echo "エラー: 商品が見つかりません"; return; } if ($quantity <= 0) { echo "エラー: 数量は1以上を指定してください"; return; } $stock = getStock($productId); if ($stock < $quantity) { echo "エラー: 在庫が不足しています"; return; } try { return Order::create(['productId' => $productId, 'quantity' => $quantity]); } catch (Exception $e) { error_log("Order creation failed: " . $e->getMessage()); echo "エラー: 注文処理に失敗しました"; } } 8/63
  4. 妥当性チェックと例外を投げることに集中する function createOrder(int $productId, int $quantity): Order { if ($quantity

    <= 0) { throw new LogicException('Quantity must be greater than 0'); } if (!productExists($productId)) { throw new NotFoundException($productId); } $stock = getStock($productId); if ($stock < $quantity) { throw new InsufficientStockException($productId, $quantity, $stock); } // DBへの保存に発生する例外はcatchしない return Order::create(['productId' => $productId, 'quantity' => $quantity]); } 11/63
  5. 状況に応じた柔軟なエラー対応が可能 try { $order = createOrder( $request->input('productId'), $request->input('quantity') ); return

    response()->json($order, 201); } catch (NotFoundException $e) { return response()->json(['error' => $e->getMessage()], 404); } catch (InsufficientStockException $e) { return response()->json(['error' => $e->getMessage()], 400); } // 他の例外はさらに呼び出し元のエラーハンドラで共通の対応 12/63
  6. function addNumbers(int $a, int $b): int { return $a +

    $b; } 呼び出し側の実装ミス = 技術的例外 引数に文字列が渡される 加算した結果が PHP がサポートする整数型の最大値を超える 20/63
  7. 呼び出し側の実装ミス = 技術的例外 function inverse(int $x) { if ($x ===

    0) { throw new LogicException('Division by zero'); } return 1/$x; } 22/63
  8. 実行時の環境やデータに依存するエラー = 技術的例外 function readConfig(string $path): array { if (!file_exists($path))

    { throw new RuntimeException('Config file not found'); } $content = file_get_contents($path); if ($content === false) { throw new RuntimeException('Failed to read config file'); } return json_decode($content, true); } 24/63
  9. ビジネスルールの実装 Entity 作成前に商品の存在と在庫をチェック 商品が見つからない場合や在庫不足の場合はドメイン例外を投げる function createOrder(ProductId $productId, Quantity $quantity): Order

    { if (!$this->product_repository->exists($productId)) { throw new NotFoundException($productId); } $stock = $this->inventory_repository->getStock($productId); if ($stock < $quantity->value()) { throw new InsufficientStockException($productId, $quantity, $stock); } // 問題ない状態で Entity が作成される return Order::create($productId, $quantity); } 37/63
  10. 入力値の妥当性確認 妥当性確認には 2 種類ある 構文的(syntactical)な妥当性確認 ドメインモデルの状態に依存しない形式チェック → ValueObject やリクエストバリデーションで実施 意味的(semantical)な妥当性確認

    ドメインモデルの現在の状態を踏まえたビジネスルール → ドメイン層で実施 手を動かしてわかるクリーンアーキテクチャ ヘキサゴナルアーキテクチャによるクリーンなアプリケーション開発 38/63
  11. 構文的な妥当性確認の実装 数量 ValueObject を作成 コンストラクタが呼ばれた段階で処理を中断できる ドメインモデルの状態に依存しない構文的なチェック 呼び出し側の実装ミスなので、 InvalidArgumentException (LogicException) を投げる

    class Quantity { public function __construct(int $value) { if ($value <= 0) { throw new InvalidArgumentException('数量は1以上を指定してください quantity: '.$value); } $this->value = $value; } } 39/63
  12. 静的解析で @throws のチェック PHPStan で指定した例外クラスをハンドリングしているかチェックできる ドメイン例外の基底クラスを指定 parameters: exceptions: checkedExceptionClasses: #

    ここで指定した例外クラス(とそのサブクラス)を「Checked Exception」として扱う # throw する場合は必ずメソッドに @throws を明記させる対象になる - Package\Domain\Exception\DomainException check: # Checked Exception を投げているのに、メソッドに @throws が書かれていない場合にエラーにする missingCheckedExceptionInThrows: true # @throws の型が抽象的すぎる場合にエラーにする(例:@throws \Exception はNG) tooWideThrowType: true 47/63
  13. 他言語のアプローチ: エラーを値として扱う Go 例外を制御構造と結びつけることは複雑な コードにつながる → エラーを値(error 型)として戻り値で返す Rust 例外が存在せず、Result<T,

    E>型で成功/失敗を表現 → 処理の失敗も「結果」の一部としてモデリング Why does Go not have exceptions? https://go.dev/doc/faq Errors are values https://go.dev/blog/errors-are-values Error Handling The Rust Programming Language https://doc.rust-lang.org/book/ch09-00-error-handling.html 54/63
  14. 戻り値として表現する手法 方法 構造 問題点 / 特徴 Union User|Exception|null 成功・失敗・欠損が混在 ・呼び出し側が

    instanceof チェック乱立 ・網羅保証がない 配列 [$user, $err] 両方null/非nullもあり得る ・排他性を型で保証できない ・ $err 無視しても動く Result<Ok, Err> どちらか一方のみ ・成功/失敗を型で明示 ・呼び出し側に分岐を強制(lint) 57/63
  15. Result 型 try-catch とは別のエラーハンドリングのための型 戻り値として「 成功 」または「 失敗 」を持つ 成功

    (Ok): 正常な値を返す 失敗 (Err): エラー情報を返す 成功した場合、失敗した場合など処理を分岐できる 関数のシグネチャから「この関数は失敗する可能性が ある」ことが明示的 呼び出し側は戻り値を取り出すためにチェックする ことが強制される Result 型を返す処理 if ($input === null) { // 失敗の場合 return Result::err('Input cannot be null'); } else { // 成功の場合 return Result::ok($input); } 結果の検証と値の取得 if ($result->isOk()) { $value = $result->unwrap(); } else { $error = $result->unwrapErr(); } 58/63