try {
orderRepository.save(order);
} catch (Exception e) {
// 나중에 처리
}
sendConfirmationEmail(order); // 저장 실패했는데 확인 메일 발송저장이 실패해도 아무 일 없다는 듯 진행된다. 고객은 확인 메일을 받았는데 주문이 없다.
try {
orderRepository.save(order);
} catch (DataAccessException e) {
throw new OrderSaveFailedException(order.id(), e); // 처리 못 하면 전파
}
sendConfirmationEmail(order);정말로 무시해도 되는 예외라면(예: 선택적 캐시 갱신 실패) 주석으로 왜 무시해도 되는지 남기고 최소한 로그를 찍는다.
catch (SQLException e) {
throw new DataAccessException("DB 오류: " + e.getMessage()); // 스택트레이스 손실
}메시지만 복사하면 어느 SQL, 어느 줄에서 났는지 알 수 없다. 장애 원인 분석이 불가능해진다.
catch (SQLException e) {
throw new DataAccessException("DB 오류: orderId=" + id, e); // cause 체이닝
}try { ... }
catch (Exception e) { ... } // 부모를 먼저
catch (IOException e) { ... } // 컴파일 에러: 이미 위에서 잡혀 도달 불가try { ... }
catch (FileNotFoundException e) { ... } // 가장 구체적
catch (IOException e) { ... }
catch (Exception e) { ... } // 가장 일반적FileInputStream in = new FileInputStream("a.txt");
byte[] data = in.readAllBytes(); // 여기서 예외 나면 close() 안 됨
in.close();try (FileInputStream in = new FileInputStream("a.txt")) {
byte[] data = in.readAllBytes(); // 예외가 나도 close() 보장
}Integer parse(String s) {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
return null; // "숫자가 아닐 수 있음"은 정상 케이스인데 예외로 처리
}
}입력의 절반이 숫자가 아니라면 절반의 호출마다 스택트레이스를 캡처한다. 예측 가능한 케이스는 사전 검사하거나 Optional로 표현한다.
Optional<Integer> parse(String s) {
if (s == null || !s.matches("-?\\d+")) return Optional.empty();
return Optional.of(Integer.parseInt(s));
}void process() throws Exception { ... } // 무엇이 실패하는지 아무 정보 없음호출자도 catch (Exception e)를 쓸 수밖에 없고, 그러면 NPE 같은 버그까지 함께 삼킨다.
void process() throws IOException, InterruptedException { ... } // 구체적으로
// 또는 unchecked 비즈니스 예외로 전환해 throws 자체를 제거