77. 예외를 무시하지 말라
✅ 핵심 요약
예외는 반드시 처리하거나, 최소한 로그 등으로 기록해야 합니다.
빈
catch
블록(예외 무시)은 매우 위험합니다.예외를 무시할 특별한 이유가 있다면, 반드시 주석을 남기고 변수명도
ignored
등으로 명확히 합시다.예외를 무시하면 프로그램이 예측 불가능한 상태에 빠질 수 있습니다.
📚 필수 개념 및 선행 지식
예외(Exception)의 기본 개념
예외(Exception): 프로그램 실행 중 발생할 수 있는 오류 상황을 객체로 표현한 것
목적: 정상적인 프로그램 흐름을 방해하는 이벤트를 처리하기 위한 메커니즘
계층 구조: Java에서 모든 예외는
java.lang.Throwable
클래스를 상속함
예외의 종류
검사 예외(Checked Exception)
Exception
클래스의 하위 클래스 중RuntimeException
을 제외한 모든 예외컴파일러가 처리를 강제하며, 메서드 시그니처에 선언되어야 함
예:
IOException
,SQLException
,ClassNotFoundException
비검사 예외(Unchecked Exception)
RuntimeException
과 그 하위 클래스,Error
와 그 하위 클래스명시적인 처리가 강제되지 않음
예:
NullPointerException
,ArrayIndexOutOfBoundsException
,IllegalArgumentException
예외 처리 메커니즘
try-catch 문: 예외가 발생할 수 있는 코드를 감싸고, 예외 발생 시 처리 방법을 정의
throw 문: 예외를 명시적으로 발생시킴
throws 절: 메서드가 처리하지 않고 호출자에게 전파할 예외를 선언
finally 블록: 예외 발생 여부와 관계없이 항상 실행되어야 하는 코드를 포함
🔑 Key 포인트
1. 빈 catch 블록은 절대 금지!
예외 은폐의 위험성
예외가 발생해도 아무런 처리를 하지 않으면, 오류가 은폐되어 문제의 원인을 찾기 어려움
표면적으로는 프로그램이 동작하는 것처럼 보이나, 잘못된 상태로 계속 실행될 수 있음
잠재적 위험
초기에는 문제가 드러나지 않다가 나중에 예기치 않은 시점에 심각한 장애로 이어질 수 있음
디버깅이 매우 어려워지고, 오류의 원인을 추적하기 힘들어짐
실무 사례
데이터베이스 연결 예외를 무시하여 데이터 불일치 발생
파일 I/O 예외를 무시하여 데이터 손실 초래
네트워크 예외를 무시하여 중요 통신 실패 은폐
2. 예외를 무시해야 한다면 반드시 이유를 명확히!
주석과 명확한 변수명의 중요성
예외를 무시해야 할 상황이라면, catch 블록에 상세한 주석으로 이유를 설명
예외 변수명을
ignored
로 명명하여 의도적으로 무시했음을 명시
예외 무시가 정당화될 수 있는 상황
파일 닫기(
close()
) 작업에서 발생하는 예외 - 주요 작업은 이미 완료스레드 중단 시
InterruptedException
- 스레드가 이미 종료 단계자원 정리 과정에서 발생하는 예외 - 주요 기능은 이미 완료
로깅의 활용
예외를 완전히 무시하지 말고, 로그 레벨을 조정하여 기록
개발/테스트 환경에서는 상세 로그, 운영 환경에서는 필요 시 로그 레벨 조정
3. 예외는 최소한 로그라도 남겨라!
로깅의 이점
문제 발생 시 디버깅을 위한 중요한 단서 제공
반복적으로 발생하는 예외 패턴 파악 가능
시스템 운영 상태 모니터링에 활용
효과적인 로깅 전략
예외 메시지, 스택 트레이스, 발생 시간, 관련 컨텍스트 정보 포함
적절한 로그 레벨 선택(ERROR, WARN, INFO, DEBUG 등)
구조화된 로깅으로 검색 및 분석 용이성 확보
로깅 프레임워크 활용
SLF4J, Logback, Log4j2 등의 로깅 프레임워크 활용
중앙화된 로그 관리 및 분석 시스템 구축
👓 예제 코드
// 잘못된 예시: 예외를 무시하는 코드 (절대 이렇게 하지 말 것!)
public void badPractice() {
try {
// 예외가 발생할 수 있는 코드
someMethod();
} catch (SomeException e) {
// 아무 처리도 하지 않음 (위험!)
}
}
// 올바른 예시 1: 예외를 로그로 남김
public void goodPractice1() {
try {
someMethod();
} catch (SomeException e) {
// 예외 발생 사실을 로그로 남긴다.
logger.error("작업 처리 중 오류 발생", e);
// 또는 단순히:
System.err.println("예외 발생: " + e.getMessage());
e.printStackTrace();
}
}
// 올바른 예시 2: 예외를 무시해야 할 때 (명확한 주석과 변수명)
public void goodPractice2() {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream("somefile.txt");
// 파일 작업 수행
} catch (IOException e) {
logger.error("파일 처리 중 오류", e);
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException ignored) {
// 파일 닫기 실패는 무시해도 됨. 이미 필요한 정보는 모두 읽었음.
// 하지만 디버깅 목적으로 로그를 남길 수도 있음
logger.debug("파일 닫기 실패", ignored);
}
}
}
}
// 더 나은 방법: try-with-resources 활용
public void betterPractice() {
try (FileInputStream fileInputStream = new FileInputStream("somefile.txt")) {
// 파일 작업 수행
} catch (IOException e) {
logger.error("파일 처리 중 오류", e);
}
// try-with-resources가 자동으로 close() 처리
}
// 올바른 예시 3: 기본값을 사용하는 상황
public int getOptimalColorsWithTimeout() {
ExecutorService exec = Executors.newSingleThreadExecutor();
Future<Integer> f = exec.submit(planarMap::chromaticNumber);
int numColors = 4; // 기본값
try {
numColors = f.get(1L, TimeUnit.SECONDS);
} catch (TimeoutException | ExecutionException ignored) {
// 기본값 사용. 색상 수를 최소화하면 좋지만, 필수는 아님.
logger.info("최적 색상 계산 시간 초과, 기본값 4 사용", ignored);
} catch (InterruptedException e) {
// 인터럽트 상태 복원
Thread.currentThread().interrupt();
// 그리고 예외 로깅
logger.warn("색상 계산 중 인터럽트 발생", e);
} finally {
exec.shutdown();
}
return numColors;
}
// 예제 4: 예외를 다시 래핑하여 던지기
public void rethrowWithContext() {
try {
riskyOperation();
} catch (LowLevelException e) {
// 더 의미 있는 예외로 변환하여 다시 던짐
throw new HighLevelException("상위 레벨 작업 실패", e);
}
}
// 예제 5: 조건부 로깅과 처리
public void conditionalLogging(boolean isVerboseMode) {
try {
riskyOperation();
} catch (SomeException e) {
if (isVerboseMode) {
// 상세 모드에서는 전체 스택 트레이스 로깅
logger.error("작업 실패: " + e.getMessage(), e);
} else {
// 일반 모드에서는 간략한 메시지만 로깅
logger.error("작업 실패: " + e.getMessage());
}
// 추가 복구 로직
performRecoveryAction();
}
}
// 예제 6: 다중 예외 처리와 차별화된 응답
public void handleMultipleExceptions() {
try {
complexOperation();
} catch (DataAccessException e) {
// 데이터 접근 관련 예외 처리
logger.error("데이터 접근 오류", e);
notifyDatabaseAdmin(e);
} catch (ValidationException e) {
// 유효성 검증 실패 예외 처리
logger.warn("입력 데이터 유효성 검증 실패", e);
requestCorrection();
} catch (SystemException e) {
// 시스템 예외 처리
logger.error("심각한 시스템 오류", e);
notifySystemAdmin(e);
// 필요하다면 재시도 또는 우아한 종료
gracefulShutdown();
}
}
🥼 더 깊이 알아보기: 예외 처리의 모범 사례
예외 처리 철학
예외는 진짜 '예외적인' 상황에만 사용해야 한다
정상적인 제어 흐름에 예외를 사용하지 말 것
예상 가능한 오류 상황은 반환 값(Optional, 상태 코드 등)으로 처리하는 것이 더 적합할 수 있음
적절한 예외 계층 구조 설계
애플리케이션 도메인에 맞는 예외 계층 구조 설계
세분화된 예외 유형으로 더 명확한 오류 처리 가능
실무에서의 예외 처리 전략
복구 가능성에 따른 처리
복구 가능한 예외: 적절한 대체 동작 수행
복구 불가능한 예외: 오류 로깅 후 관리자에게 알림
트랜잭션 처리와 예외
트랜잭션 경계에서의 예외 처리 전략 수립
롤백 정책과 예외 유형 연결
예외 전환(Exception Translation)
하위 계층의 예외를 상위 계층에 맞는 예외로 변환
원인 예외는 반드시
cause
로 연결하여 정보 유지
성능과 예외 처리
예외의 성능 영향
예외 생성과 스택 트레이스 수집은 비용이 큰 작업
정상 흐름 제어에 예외를 사용하면 성능 저하 발생
예외 처리 최적화
핫 경로(Hot Path)에서는 예외 발생을 최소화
불필요한 스택 트레이스 생성 피하기
🎯 결론
예외를 무시하면 안 된다. 예외는 반드시 처리하거나, 최소한 로그로 남겨야 합니다.
예외를 무시해야 할 특별한 상황이라면, 반드시 그 이유를 주석으로 남기고 변수명도
ignored
로 명확히 합시다.예외를 무시하면 프로그램이 심각한 오류를 내포한 채 동작할 수 있으므로, 항상 신중하게 다뤄야 합니다.
적절한 예외 처리는 견고한 애플리케이션의 핵심 요소입니다.
Last updated