숏컷 코드
try {
Files.readString(path);
} catch (IOException e) {
System.out.println("읽기 실패: " + e.getMessage());
}문법
어떤 예외 흐름을 먼저 떠올리면 되나
| 상황 | 먼저 떠올릴 것 |
|---|---|
| 외부 I/O, 복구 가능한 오류 | checked exception |
| 프로그래밍 오류, 잘못된 인수 | unchecked exception |
| 여기서 처리 가능 | try-catch |
| 상위가 더 잘 처리함 | throws |
| 의미를 바꿔 전달 | wrapping |
checked vs unchecked: 컴파일러가 강제하느냐
checked exception은 Exception을 상속하지만 RuntimeException은 아닌 예외로, 컴파일러가 처리(try-catch) 또는 선언(throws)을 강제합니다. IOException, SQLException이 대표적입니다. unchecked exception은 RuntimeException 계열로 강제가 없습니다. NullPointerException, IllegalArgumentException, IndexOutOfBoundsException 등이 해당됩니다.
// checked — 컴파일러가 처리를 강제
void readFile(Path path) throws IOException { // 명시적 선언 필요
Files.readString(path);
}
// unchecked — 강제 없음, 프로그래밍 오류를 나타낼 때
void process(String input) {
if (input == null) throw new IllegalArgumentException("input must not be null");
}try-catch vs throws: 어디서 처리할 것인가
try-catch는 "이 계층에서 처리하겠다"는 선택이고, throws는 "호출자에게 책임을 넘기겠다"는 선택입니다. 어느 쪽이 맞는지는 "이 계층이 이 오류를 의미 있게 처리할 수 있는가"에 달려 있습니다. DB 접근 오류를 서비스 레이어에서 처리하는 것과, 컨트롤러에서 HTTP 응답으로 변환하는 것은 다른 결정입니다.
// 서비스 레이어: IOException을 도메인 예외로 변환
User loadUser(long id) {
try {
return repository.findById(id);
} catch (IOException e) {
throw new UserLoadException("사용자 로드 실패: " + id, e); // 의미 있는 변환
}
}
// 레포지터리 레이어: 처리 불가 → 선언만
User findById(long id) throws IOException { ... }예외 계층 설계: 삼키거나 무조건 던지지 않기
예외를 catch만 하고 아무 처리 없이 삼키면 실패 원인을 추적하기 어려워집니다. 반대로 모든 계층이 그대로 throws만 하면 호출자가 어떤 의미의 실패인지 파악하기 어렵습니다. 각 계층이 "이 예외를 의미 있게 다룰 수 있는가"를 판단해 처리 또는 변환합니다.
// ❌ 예외 삼키기 — 실패가 조용히 묻힘
try {
sendEmail(user);
} catch (Exception e) {
// 아무것도 하지 않음 — 원인 추적 불가
}
// ✅ 로깅 + 재던지기 또는 의미 있는 처리
try {
sendEmail(user);
} catch (EmailException e) {
log.error("이메일 발송 실패: userId={}", user.getId(), e);
throw new NotificationException("알림 발송 실패", e);
}체크포인트
| 상황 | 적합한 선택 |
|---|---|
| I/O, 네트워크처럼 복구 가능한 외부 오류 | checked exception |
| 프로그래밍 오류, 잘못된 입력 | unchecked (RuntimeException) |
| 이 계층에서 의미 있게 처리 가능 | try-catch |
| 호출자가 더 잘 처리할 수 있는 경우 | throws 선언 |
| 계층 간 예외 변환 | catch 후 도메인 예외로 wrapping |
주의할 점
예외를 catch만 하고 아무것도 하지 않으면 실패 원인을 추적할 수 없습니다. 최소한 로깅이라도 해야 합니다.
// ❌ 예외를 삼키는 코드 — 버그 원인 추적 불가
try {
processOrder(order);
} catch (Exception e) {
// 아무것도 하지 않음
}
// ✅ 로깅 + 의미 있는 재처리
try {
processOrder(order);
} catch (PaymentException e) {
log.error("주문 처리 실패: orderId={}", order.getId(), e);
order.markAsFailed(e.getMessage());
}참고 링크
2 sources