Reference/Effective-Java

[Effective-Java] Chapter10 #72. 표준 예외를 사용하라

나죽못고나강뿐 2023. 7. 31. 13:09
📌 장점
  • Java 라이브러리는 대부분 API에서 쓰기 충분한 수의 예외를 제공하니 재활용하라
  • 우리가 작성한 API가 다른 사람이 익히고 사용하기 쉬워진다.
  • 우리의 API를 사용하는 프로그램도 낯선 예외를 사용하지 않게 되어 읽기 쉬워진다.
  • 예외 클래스 수가 적을 수록 memory 사용량이 줄고 클래스를 적재하는 시간이 감소한다.

 

📌 종류
예외 주요 쓰임
IllegalArgumentException 허용하지 않는 값이 인수로 건네졌을 때(null은 따로 NullPointerException으로 처리)
IllegalStateException 객체가 메서드를 수행하기에 적절하지 않은 상태일 때
NullPointerException null을 허용하지 않는 메서드에 null을 건넸을 때
IndexOutOfBoundsException 인덱스가 범위를 넘어섰을 때
ConcurrentModificationException 허용하지 않는 동시 수정이 발견됐을 때
UnsupportedOperationException 호출한 메서드를 지원하지 않을 때
인수 값이 무엇이었든 어차피 실패했을 거라면 IllegalStateException을, 그렇지 않으면 IllegalArgumentException을 던지자.
public class MainRunner {
    List<Integer> list = new ArrayList<>();
  
    public void positiveAdd(Integer number) { // 매개변수 값에 따라 예외 결정
        if (number < 0) {
            throw new IllegalArgumentException("음수값은 허용하지 않음");
        }
        list.add(number);
    }

    public static void main(String[] args) {
        MainRunner mainRunner = new MainRunner();
        mainRunner.positiveAdd(-1);
    }
}
  •  IllegalArgumentException
    • 가장 많이 재사용되는 예외
    • 호출자가 인수로 부적절한 값을 넘길 때 사용한다.
    • ex. 반복 횟수를 지정하는 매개변수에 음수를 건넬 때

 

public class MainRunner {
    List<Integer> list = new ArrayList<>();
    
    public void evenIndexAdd(Integer number){ // 인스턴스 상태에 따라 예외 결정
        if(list.size()%2 != 0){
            throw new IllegalStateException("짝수 인덱스에 들어갈 준비가 되지 않음");
        }
        list.add(number);
    }

    public static void main(String[] args) {
        MainRunner mainRunner = new MainRunner();
        mainRunner.evenIndexAdd(0);
        mainRunner.evenIndexAdd(1);
    }
}
  • IllegalStateException
    • 대상 객체의 상태가 호출된 메서드를 수행하기에 적합하지 않을 때
    • ex. 제대로 초기화되지 않은 객체를 사용

 

public class MainRunner {
    List<Integer> list = new ArrayList<>();

    public void add(Integer number){
        if(Objects.isNull(number)){
            throw new NullPointerException("값이 null 입니다.");
        }
        list.add(number);
    }

    public static void main(String[] args) {
        MainRunner mainRunner = new MainRunner();
        mainRunner.add(null);
    }
}
public class MainRunner {
    List<Integer> list = new ArrayList<>();

    public void add(Integer number){
        if(list.size()>1){
            throw new IndexOutOfBoundsException("요소를 3개 이상 설정 할 수 없습니다.");
        }
        list.add(number);
    }

    public static void main(String[] args) {
        MainRunner mainRunner = new MainRunner();
        mainRunner.add(1);
        mainRunner.add(2);
        mainRunner.add(3);
    }
}
  • 메서드가 던지는 모든 예외를 위의 두 예외로 뭉뚱그릴 수 있지만, 특수한 일부는 따로 구분해 쓴다.
    • null 값을 허용하지 않는 메서드에 null을 건네면 관례상 NPE를 던진다.
    • 어떤 시퀀스 허용 범위를 넘는 값을 건넬 때도 IndexOutOfBoundsException을 건넨다.
  • ConcurrentModificationException
    • Single Thread 목적의 객체를 여러 Thread가 동시 수정하려 할 때 던진다.
    • 외부 동기화 방식으로 사용하려고 설계한 객체에서도 마찬가지다.
    • 동시 수정을 확실히 검출할 안정된 방법은 없으므로, 해당 예외는 문제가 생길 가능성을 알려주는 정도로만 쓰인다.
  • UnsupportedOperationException
    • Client가 요청한 동작을 대상 객체가 지원하지 않을 때 던진다.
    • 흔한 경우는 아니다. (대부분 객체는 자신이 정의한 메서드를 모두 지원하므로)
    • ex. 원소를 넣을 수만 있는 List 구현체에 remove 메서드를 호출한 경우
💡 Exception, RuntimeException, Throwable, Error는 직접 재사용하지 마라
  • 위 클래스들은 추상 클래스 정도로 여기는 것이 좋다.
  • 다른 예외들의 상위 클래스이므로, 여러 성격의 예외들을 포괄하고 있어 안정적인 테스트가 힘들다.

 

📌 다른 예외들
  • 복소수나 유리수를 다루는 객체 : ArithmeticException, NumberFormatException
  • 상황에 부합하다면 항상 표준 예외를 재사용하자.
    • 단, 예외의 이름과 예외가 던져지는 맥락도 부합해야만 한다.
  • 더 많은 정보를 포함하고 싶다면 표준 예외를 확장해도 좋다.
    • 예외도 클래스이므로 직렬화할 수 있다.
    • 직렬화에는 많은 부담이 따르므로, 커스텀 예외를 만드는 게 좋은 방안은 아니다.