JAVA/Effective Java

[아이템 69] 예외는 진짜 예외 상황에만 사용하라

꾸준함. 2024. 3. 21. 06:08

예외를 안 써도 되는 상황에서는 예외를 사용하지 말자

 

 

 

코드가 달성하고자 한 목표

  • JVM은 배열에 접근할 때마다 인덱스가 범위를 넘지 않는지 검사
  • 일반적인 반복문도 배열 범위의 경계에 도달하면 종료
  • 일반적인 반복문을 사용하면 검사를 두 번하므로 두 검사 중 하나를 생략함으로써 성능 최적화를 목표함

 

위 코드의 문제점

  • 코드 가독성 저하
  • 잘못된 추론을 근거로 성능 최적화를 노렸고 잘못되었다는 근거는 다음과 같음
    • 예외는 예외 상황에 사용할 용도로 설계되었으므로 JVM 구현자 입장에서는 최적화에 별로 신경 쓰지 않았을 가능성이 큼
    • try-catch 블록 내 코드는 JVM이 적용할 수 있는 최적화가 제한됨
    • 배열을 순회하는 표준 관용구는 앞서 걱정한 중복 검사를 수행하지 않음 (JVM이 알아서 최적화)

 

1. 예외를 사용한 쪽이 표준 관용구보다 훨씬 느림

 

실제로 테스트해 본 결과 예외를 사용한 쪽이 표준 관용구를 사용한 반복문보다 훨씬 느렸습니다.

 

 

2. 예외를 사용한 쪽이 느릴 뿐만 아니라 코드가 의도한 대로 동작하지 않을 수 있음

 

  • 반복문 내 버그가 숨어있을 경우 흐름 제어에 쓰인 예외가 해당 버그를 숨기기 때문에 디버깅을 어렵게 만듦
    • 예외가 발생한 부분에서 실제로 문제가 발생한 것인지, 아니면 그 이전의 코드에서 문제가 발생한 것인지 확인하기 어려울 수 있음
    • 반복문은 동일한 코드 블록을 반복적으로 실행하기 때문에 여러 번의 반복이 일어날 수 있으며 이러한 상황에서 어느 반복에서 문제가 발생했는지 정확히 파악하기 어려움

 

반복문에서 호출한 메서드가 내부에서 관련 없는 배열을 사용했다가 `ArrayIndexOutOfBoundsException` 예외를 던졌다고 가정했을 때

  • 표준 관용구는 예외를 잡지 않고 스택에 정보를 로깅 후 쓰레드를 즉각 종료
  • 반면, 예외를 사용한 반복문에서는 버그 때문에 발생한 엉뚱한 예외를 정상적인 반복문 종료 상황으로 오해하고 넘어감 (예외를 잡고 던지지 않았기 때문에)

 

3. 예외는 예외 상황에서만 사용하고 일반적인 제어 흐름용으로 쓰지 말자

 

  • 일반적인 제어 흐름은 표준 관용구인 for 문을 사용할 것
  • 앞서 예시 코드처럼 성능 개선을 목적으로 예외 사용하는 것을 지양해야 함
    • 실제 성능이 좋아지더라도 자바 버전이 올라가면서 최적화를 진행하기 때문에 성능 우위가 오래가지 않을 확률이 높음
    • 반면, 버그의 폐해와 유지보수 문제는 지속될 확률이 높음

 

4. 잘 설계된 API는 클라이언트가 정상적인 제어 흐름에서 예외를 사용하지 말자

 

  • 특정 상태에서만 호출할 수 있는 `상태 의존적`인 메서드를 제공할 경우 `상태 검사` 메서드도 함께 제공해야 함
    • ex) Iterator 인터페이스의 `next`와 `hasNext`가 각각 상태 의존적 메서드와 상태 검사 메서드에 해당

 

 

4.1 지양해야 하는 코드 스타일

 

 

4.2 상태 검사 메서드 대신 사용할 수 있는 선택지

 

올바르지 않은 상태일 때 빈 Optional 혹은 null 같은 특수한 값을 반환하는 방법도 존재합니다.

  • 동기화 없이 여러 쓰레드가 동시에 접근할 수 있거나 외부 요인으로 상태가 변할 수 있다면 옵셔널이나 특정 값을 사용
  • 성능이 중요한 상황일 경우 상태 검사 메서드가 상태 의존적 메서드의 작업 일부를 중복 수행하기 때문에 Optional이나 특정 값 선택

 

예시를 들자면 다음과 같습니다.

  • 빈 Optional 반환: 메서드가 올바르지 않은 상태라고 판단되면 빈 Optional을 반환
    • 이렇게 함으로써 메서드 호출자는 반환된 Optional이 비어있는지 여부를 확인하고 적절히 처리
    • 이 방법은 명시적이며, 상태 검사 메서드를 호출한 후에 반환된 값으로 처리를 수행하는 대신, Optional을 반환하여 상태를 명확하게 표현 가능

 

  • null 반환: Java에서는 null을 사용하여 참조형 변수의 값이 없음을 표현
    • 메서드가 올바르지 않은 상태라고 판단되면 null을 반환하여 호출자에게 상태를 알릴 수 있음
    • 하지만 이 방법은 NullPointerException을 유발할 수 있으므로 주의해서 사용

 

  • 특정 값을 반환: 때로는 특정 값을 사용하여 상태를 나타낼 수 있음
    • ex) -1을 사용하여 유효하지 않은 인덱스를 나타내거나, 빈 문자열을 사용하여 유효하지 않은 문자열을 나타낼 수 있음
    • 이러한 접근 방식은 Optional보다는 덜 안전하고 명시적이지 않을 수 있으므로 사용에 주의

 

위에서 언급한 두 케이스를 제외하고는 상태 검사 메서드 방식이 더 나은 방식입니다.

  • 가독성 측면에서 우위이고 오류를 발견하기 쉬움
  • 상태 검사 메서드 호출을 누락했을 경우 상태 의존적 메서드가 예외를 던져 버그를 확실히 드러낼 수 있음

 

참고

이펙티브 자바

반응형