迷わない!Javaのtry-catch設計|どこで捕まえて、どこで投げる?

こんにちは、さるまりんです 🐒🔧

Javaで開発をしていると、必ず出会うのが「例外処理」。

でも…

  • とりあえずcatchしている
  • printStackTraceだけ書いている
  • どこで処理すればいいかわからない

こんなふうに、なんとなく書いてしまっていること、ありませんか?

実はtry-catchは「文法」ではなく、設計の話です。

人それぞれの書き方になりがちな部分ですが、あらかじめ決まったルールを持つと楽になることがあります。

今回は、迷わないために考え方を整理していきます。


try-catchの基本

こんなふうに書きます。

try {
    // 処理
} catch (Exception e) {
    // エラー処理
}
  • try:例外が起きるかもしれない処理
  • catch:例外を捕まえる

Javaをやっていたら、どこかで見たことがありますよね?


よくあるNGパターン

❌ とりあえずcatch

try {
    process();
} catch (Exception e) {
    e.printStackTrace();
}

一見ちゃんと書いているように見えますが…

👉 何も解決していません

  • ログが不十分
  • エラーが上に伝わらない
  • 原因調査が難しくなる

ただ出力しているだけで、これで問題を解決するのは難しいことが多いです。

❌ catchしすぎる

try {
    serviceA();
    serviceB();
    serviceC();
} catch (Exception e) {
    // まとめて処理
}

👉 問題

  • どこで失敗したのか分からない
  • 原因切り分けが難しくなる

どこで何が起こったかが分かりにくくなります。
エラーが発生しても続けてやらなきゃいけない処理にも到達しないかもしれません。


基本ルール(ここがいちばん大事)

ここを押さえればOKです👇


① 処理できる場所でだけcatchする

👉 これが一番大事です

  • リトライできる
  • ユーザーにメッセージを返せる
  • 代替処理ができる

こういう場合だけcatchします。

② 処理できないなら上に投げる

public void readFile() throws IOException {
    fileService.read();
}

👉 無理にcatchしない

この場合は読めないのだから、呼び出した側で読めなかった時にどうするかを決めてもらいます。

③ catchしたら「意味のある処理」をする

  • ログ出力
  • リトライ
  • メッセージ変換

👉 「捕まえるだけ」はNG

エラーの時に何をするか。ここがとても重要です。


実務でよく使うパターン

パターン①:ログを出して再throw

try {
    process();
} catch (Exception e) {
    log.error("処理失敗", e);
    throw e;
}

👉 よく使います。
ログは発生した箇所で記録し、呼び出し元にもエラーが起こったことを伝えています。

パターン②:例外を変換する

try {
    repository.save(data);
} catch (SQLException e) {
    throw new RuntimeException("DB保存に失敗しました", e);
}

👉 技術的な例外 → 業務的な意味へ変換

技術的には「データベースに書き込めなかった」、意味としては「保存に失敗」。
翻訳作業に近いかもしれないですね。

パターン③:ユーザー向けメッセージ

try {
    service.execute();
} catch (Exception e) {
    return "エラーが発生しました";
}

👉 UIやAPIでよくある形
理解できるエラーメッセージを返却しています。


throwsとtry-catchの使い分け

迷ったらこれでOKです👇

状況 対応
自分で処理できる catch
処理できない throws

👉 シンプルに考えましょう


finallyって何?

try {
    process();
} catch (Exception e) {
    log.error("エラー", e);
} finally {
    close();
}

finallyは、

👉 例外が起きても起きなくても必ず実行される処理

上の方でも触れた「失敗してもやらなきゃいけないこと」です。

よくある使い道

  • ファイルクローズ
  • DB接続の解放

ただし今は…

Javaでは try-with-resources が主流です👇

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
}

👉 自動でcloseしてくれるので安全

br.close() を明示的に書かなくても自動でやってくれます。
リソース解放まわりをシンプルにできるのが大きなメリットです。


おすすめの書き方

実際にはこんな構造で書けると使いやすいです。

try {
    service.execute();
} catch (BusinessException e) {
    log.warn("業務エラー", e);
    throw e;
} catch (Exception e) {
    log.error("予期しないエラー", e);
    throw new RuntimeException(e);
}

👉 ポイント

  • 業務エラーとシステムエラーを分ける
  • ログレベルを変える
  • 最後は必ず上に伝える

まとめ

  • try-catchは「設計」
  • むやみにcatchしない
  • 処理できる場所でだけ捕まえる
  • それ以外は上に投げる

👉 これだけでコードの質がかなり変わってきます。


おわりに

例外処理は地味ですが、

👉 「書き方の差がそのまま品質の差になる」

ポイントでもあります。

少しずつでも、「なぜここでcatchするのか」を意識するだけで、コードの見え方が変わってきます 👍

読んでくださってありがとうございました。
それではまた!