77. 예외를 무시하지 말라

✅ 핵심 요약

  • 예외는 반드시 처리하거나, 최소한 로그 등으로 기록해야 합니다.

  • catch 블록(예외 무시)은 매우 위험합니다.

  • 예외를 무시할 특별한 이유가 있다면, 반드시 주석을 남기고 변수명도 ignored 등으로 명확히 합시다.

  • 예외를 무시하면 프로그램이 예측 불가능한 상태에 빠질 수 있습니다.

📚 필수 개념 및 선행 지식

예외(Exception)의 기본 개념

  • 예외(Exception): 프로그램 실행 중 발생할 수 있는 오류 상황을 객체로 표현한 것

  • 목적: 정상적인 프로그램 흐름을 방해하는 이벤트를 처리하기 위한 메커니즘

  • 계층 구조: Java에서 모든 예외는 java.lang.Throwable 클래스를 상속함

예외의 종류

  1. 검사 예외(Checked Exception)

    • Exception 클래스의 하위 클래스 중 RuntimeException을 제외한 모든 예외

    • 컴파일러가 처리를 강제하며, 메서드 시그니처에 선언되어야 함

    • 예: IOException, SQLException, ClassNotFoundException

  2. 비검사 예외(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