Java
Z
信頼度ランク
| S | 公式ソース確認済み |
| A | 成功実績多数・失敗例少数 |
| B | 賛否両論 |
| C | 動作未確認・セキュリティリスク高 |
| Z | 個人所感 |
Javaの例外処理ベストプラクティス完全版
try-catchの書き方、検査例外と非検査例外の使い分け、カスタム例外の作り方、try-with-resourcesまで解説します。
一言結論
例外はログして再スローするか、完全に処理するかのどちらかにすべきで、catchして握りつぶす(何もしない)のが最悪のパターンであり障害調査を著しく困難にする。
例外の種類
Throwable
├── Error(プログラムで対処しない)
│ ├── OutOfMemoryError
│ └── StackOverflowError
└── Exception
├── RuntimeException(非検査例外)
│ ├── NullPointerException
│ ├── IllegalArgumentException
│ └── IndexOutOfBoundsException
└── IOException(検査例外)
├── FileNotFoundException
└── ...
検査例外(Checked Exception)
コンパイラが catch または throws 宣言を強制する例外。IOException、SQLException など。
非検査例外(Unchecked Exception)
RuntimeException のサブクラス。プログラミングミスを示すものが多い。
基本的な try-catch
try {
String text = readFile("data.txt");
process(text);
} catch (FileNotFoundException e) {
System.err.println("ファイルが見つかりません: " + e.getMessage());
} catch (IOException e) {
System.err.println("読み込みエラー: " + e.getMessage());
} finally {
// 必ず実行される(リソース解放など)
cleanup();
}
try-with-resources(Java 7以降)
Closeable/AutoCloseable を実装したリソースは自動でクローズされます。
// 旧来の書き方(finally でクローズ)
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("data.txt"));
String line = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try { reader.close(); } catch (IOException e) { }
}
}
// try-with-resources(推奨)
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line = reader.readLine();
} catch (IOException e) {
throw new AppException("ファイル読み込みに失敗しました", e);
}
Multi-catch(Java 7以降)
try {
// ...
} catch (FileNotFoundException | DatabaseException e) {
log.error("データ取得エラー: {}", e.getMessage());
}
カスタム例外の作り方
// アプリケーション固有の基底例外
public class AppException extends RuntimeException {
private final ErrorCode errorCode;
public AppException(String message, ErrorCode errorCode) {
super(message);
this.errorCode = errorCode;
}
public AppException(String message, ErrorCode errorCode, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
}
public ErrorCode getErrorCode() {
return errorCode;
}
}
public enum ErrorCode {
USER_NOT_FOUND,
INVALID_INPUT,
SYSTEM_ERROR
}
// 使用例
public User findUser(int id) {
User user = repository.find(id);
if (user == null) {
throw new AppException("ユーザーが見つかりません: " + id, ErrorCode.USER_NOT_FOUND);
}
return user;
}
ベストプラクティス
1. 例外をスワローしない
// NG: 例外を無視する
try {
process();
} catch (Exception e) {
// 何もしない(絶対にやってはいけない)
}
// OK: 少なくともログを出す
try {
process();
} catch (Exception e) {
log.error("処理中にエラーが発生しました", e);
throw e;
}
2. 原因例外を連鎖させる
// NG: 原因を失う
try {
repository.save(entity);
} catch (SQLException e) {
throw new AppException("保存に失敗しました"); // 原因が消える
}
// OK: 原因をラップする
try {
repository.save(entity);
} catch (SQLException e) {
throw new AppException("保存に失敗しました", e); // e を渡す
}
3. 具体的な例外をキャッチする
// NG: 広すぎる
catch (Exception e) { ... }
// OK: 具体的に
catch (FileNotFoundException e) { ... }
catch (IOException e) { ... }
4. 例外でフロー制御をしない
// NG: 例外でループを終わらせる
try {
for (int i = 0; ; i++) {
process(array[i]); // ArrayIndexOutOfBoundsException を期待
}
} catch (ArrayIndexOutOfBoundsException e) {
// ループ終了
}
// OK: 条件で制御
for (int i = 0; i < array.length; i++) {
process(array[i]);
}
5. 引数の検証は IllegalArgumentException を使う
public void deposit(int amount) {
if (amount <= 0) {
throw new IllegalArgumentException("金額は正の値である必要があります: " + amount);
}
// ...
}
ログの書き方
// スタックトレースを含めてログ出力(SLF4J)
log.error("エラーが発生しました", e); // OK: {} は不要
// NG: printStackTrace() の直接呼び出し(本番環境では非推奨)
e.printStackTrace();
まとめ
| すること | しないこと |
|---|---|
| 原因例外を連鎖させる | 例外を無視する(空のcatch) |
| 具体的な例外をキャッチ | Exception で一括catch |
| try-with-resources を使う | finallyでクローズ忘れ |
| メッセージに具体情報を含める | 「エラーが発生しました」だけ |