はじめに

Javaの例外処理(Exception Handling)は、初学者にとって理解しづらいテーマです。
特に以下のような疑問をよく見かけます:

  • なぜ例外を投げるのか?戻り値ではダメなのか?
  • catchする場所の判断基準は?
  • throwsを書くときと書かないときの違いは?

これらの混乱の多くは、「例外が何を意味していて、どう運ばれていくのか」という設計上の責務が理解できていないことに起因します。

この記事では、例外処理の本質を「郵便配達」という比喩で解説しながら、責務の所在と例外の流れを設計視点で整理していきます。

例外処理は「配達物の扱い」に似ている

まず、例外の流れを「郵便物」としてイメージしてみてください。

  • 例外を「発生」させる:郵便を出す
  • 例外を「伝播」させる:配達を引き継ぐ
  • 例外を「捕捉」する:荷物を受け取る
  • 例外を「握り潰す」:届いた郵便を捨てる
  • 例外を「再送する」:別の人に転送する

この比喩により、throw, try-catch, throwsの関係が自然に理解できます。

正しい責務分離から見る例外処理

郵便を出す側(例外発生者)
public class MailSender {
    public void send() throws MailDeliveryException {
        // 宛先が無効だった場合は例外を投げる
        throw new MailDeliveryException("Invalid address");
    }
}
宛先不明の処理を引き受ける側(上位層でcatch)
public class DeliveryService {
    private final MailSender sender = new MailSender();

    public void deliver() {
        try {
            sender.send();
        } catch (MailDeliveryException e) {
            System.out.println("例外処理:郵便配達失敗 → 上位で対応");
        }
    }
}
カスタム例外クラス
public class MailDeliveryException extends Exception {
    public MailDeliveryException(String message) {
        super(message);
    }
}

この構成では:

  • MailSenderは責務として「例外を投げる」だけ
  • DeliveryServiceが「例外を受け取って処理する」
UML Diagram

throwsとは何か:「この手紙は投げられる可能性あり」という宣言

throwsは、「このメソッドは特定の例外を投げる可能性があるので、呼び出す側はそれを考慮せよ」という契約です。

throwsの意味
public void send() throws MailDeliveryException {
    // 例外を起こす可能性あり
}
設計補足:throwsは責務の宣言

throwsを明記することは、「このメソッドはこの責務を果たせないかもしれない」という契約の宣言です。
呼び出し側がその責任をどう処理するかを明示的に設計できます。

catchの場所を間違えるとどうなるか?

NG例:MailSender側で握り潰す
public void send() {
    try {
        // 何かの処理
    } catch (Exception e) {
        // 何もしない or ログだけで終了
    }
}

このような設計だと、呼び出し側にとって何も問題が起きなかったように見えてしまうため、後続処理が想定外の状態で進んでしまいます。

責務が遮断されるcatchの罠

「catchして終わらせる」パターンは、例外の通知を途中で断ち切る設計です。
結果として、異常状態が上位層に伝わらず、誤動作の温床になります。

よくある例外処理の誤解とそのレビュー観点

初心者がやりがちなNG設計パターン
  • 例外を握り潰して処理不能を隠す
  • catch内でログだけ残し何も伝播しない
  • throwsの使い方が統一されていない(設計者の意図不在)
レビュー観点チェックリスト
設計観点:例外は「責務の通知」

例外設計はエラーハンドリングではなく「責務の通知システム」です。
構造のどこで処理すべきかが曖昧な場合、例外の流れ自体が設計不明になります。

まとめ:例外処理は責務の通知と伝達

Javaの例外処理は、単なるエラー処理ではなく責務の明示と通知のための機構です。

  • throw = 例外を発生させる(郵便を出す)
  • throws = 発生の可能性を宣言する(郵便が届く可能性を伝える)
  • catch = 対処責任を負う(郵便を受け取り対応する)

例外の構造が見える設計こそ、読みやすさと保守性のカギです。
レビューでは、流れを止める箇所が適切か、設計上の責務が正しく伝達されているかに注目すべきです。