Javaの例外処理ベストプラクティスを徹底解説!初心者が覚えるべきエラーハンドリングの基本
生徒
「Javaの例外処理って、基本的な使い方はわかるけど、実際に現場でどう書けばいいのか迷います…」
先生
「確かに例外処理は単にtry-catchで囲めばいいというものではありません。設計やメンテナンスの観点からも工夫が必要です。」
生徒
「じゃあ、Javaで例外を適切に処理するにはどんなことを意識すればいいですか?」
先生
「よし、Javaの例外処理におけるベストプラクティスをひとつずつ解説していこう!」
1. Javaの例外処理とは?基本構文を再確認
Javaの例外処理は、プログラム実行中に発生するエラーを安全に処理するための仕組みです。代表的な構文はtry-catch-finallyです。これを使って、予期しない例外が発生してもプログラムが停止しないように対応できます。
try {
// エラーが発生する可能性のある処理
} catch (IOException e) {
// エラー処理
} finally {
// リソースの解放など
}
この基本構文を正しく使うことが、例外処理の第一歩です。
2. キャッチする例外は絞り込む
ExceptionやThrowableなど、上位のクラスをcatchすると、あらゆる例外をキャッチできますが、それは好ましくありません。なぜなら、予期せぬ例外まで吸収してしまい、問題を見逃す原因になるからです。
なるべく具体的な例外クラスをcatchするようにしましょう。
try {
FileReader reader = new FileReader("sample.txt");
} catch (FileNotFoundException e) {
System.out.println("ファイルが見つかりません: " + e.getMessage());
}
例外の種類に応じて適切に処理を分けることで、可読性と保守性が向上します。
3. エラーメッセージは具体的に出力する
Javaの例外処理では、エラーが起きたときにユーザーや開発者に伝えるメッセージを適切に設計することが重要です。ただ「エラーが発生しました」だけでは、原因が特定できず、デバッグが難しくなります。
スタックトレースを表示したり、getMessage()で詳細を出力するのが一般的です。
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
e.printStackTrace();
}
開発時にはprintStackTrace()、運用時にはログ出力に切り替えるなどの工夫も大切です。
4. チェック例外と非チェック例外の違いを理解する
Javaの例外には「チェック例外(checked exception)」と「非チェック例外(unchecked exception)」があります。チェック例外はコンパイル時に例外処理を強制されるもので、たとえばIOExceptionなどが該当します。一方、NullPointerExceptionなどの非チェック例外は、実行時に発生するため、事前の処理が求められます。
どちらの種類の例外を扱っているのか意識することで、より堅牢なコードが書けるようになります。
5. throwsの使い方と責任の分離
メソッドで発生する例外を呼び出し元に伝えるにはthrowsを使います。ただし、例外を全て投げっぱなしにすると、呼び出し元での処理が煩雑になります。
そのため、「どのレイヤーで例外処理をするのか」を設計段階でしっかり決めておくことが大切です。
public void readFile(String path) throws IOException {
FileReader reader = new FileReader(path);
}
このように、例外処理の責任を明確にすることが、読みやすく保守しやすいコードの鍵になります。
6. リソースはtry-with-resourcesで安全にクローズ
ファイルやDBなどのリソースを扱うときは、try-with-resources構文を使うことで、例外が発生しても自動でclose()が呼ばれます。これはリソースリークを防ぐうえで非常に重要なテクニックです。
try (FileReader reader = new FileReader("sample.txt")) {
// ファイルの読み込み処理
} catch (IOException e) {
e.printStackTrace();
}
この構文はJava 7以降で導入された非常に便利な仕組みなので、初心者の方も早めに慣れておきましょう。
7. 例外のラップとカスタム例外クラスの活用
Javaでは、他の例外を内包して新たな例外としてスローする「ラップ」もよく使われます。ビジネスロジック層でIOExceptionなどの具体的な例外を扱いたくない場合、独自の例外クラスを用意することが有効です。
public class MyAppException extends Exception {
public MyAppException(String message, Throwable cause) {
super(message, cause);
}
}
try {
throw new IOException("ファイル読み込み失敗");
} catch (IOException e) {
throw new MyAppException("業務処理での失敗", e);
}
このようにラップすることで、例外の意味や文脈をより明確に伝えることができます。
8. Javaの例外処理はログ出力とセットで設計する
例外が発生したとき、単にprintStackTrace()で出力するだけでなく、ログファイルに記録することで、後から原因分析しやすくなります。java.util.loggingやSLF4Jなどのライブラリを使えば、柔軟なログ出力が可能になります。
実運用を見据えた設計では、例外処理とログ出力は必ずセットで考えるようにしましょう。
9. 例外処理のテストも忘れずに
Javaでユニットテストを行う際は、例外が発生するケースもカバーする必要があります。たとえばJUnitを使えば、例外が正しくスローされるかどうかを確認するテストを書くことができます。
@Test(expected = IOException.class)
public void testIOException() throws IOException {
FileReader reader = new FileReader("notfound.txt");
}
こうした例外パターンの網羅も、品質の高いJavaプログラムを作るうえで大切なポイントです。