티스토리 뷰

JAVA

[Java] 표준 예외를 사용하자

Gray__ 2023. 3. 26. 18:27

표준 예외란?

자바 API에서 제공하는 예외를 말한다. 대표적으로 IllegalArgumentException, IllegalStateException 등이 있다.

표준 예외를 사용하면 가독성이 높아지고, 사용하기 쉬워진다. 다른 프로그래머들에게 이미 익숙해진 규약을 그대로 따르기 때문이다. 표준 예외를 사용하면, 직접 구현하는 예외 클래스 개수가 적어진다. 따라서 메모리 사용량도 줄어들고, 클래스를 메모리에 적재하는 시간도 적게 걸린다.


대표적인 표준 예외

1. IllegalArgumentException

호출자가 인수로 부적절한 값을 넘길 때 던지는 예외이다. 예를 들어 Crew의 나이를 입력 할당하는 메서드에서 음수가 할당되는 경우이다.

public void setAge(int age) {
	if (age < 0) {
    	    throw new IllegalArgumentException("나이는 음수가 될 수 없습니다.")
    	}
	this.age = age;
}

 

2. IllegalStateException

대상 객체의 상태가 호출된 메서드를 수행하기에 적절하지 않을 때 발생시킬 수 있는 예외이다. 예를 들어 체스 게임을 진행하는데 체스판이 생성되지 않은 경우이다.

public void start() {
	if (this.board == null) {
    	throw new IllegalStateException("보드판이 준비되지 않았습니다.");
    }
}

 

3. NullPointerException

null 값을 허용하지 않는 메서드에 null을 건낼 때 발생시킬 수 있는 예외이다. IllegalArgumentException를 발생시킬 수도 있지만, 관례상 null을 건네면 NullPointerException을 발생시킨다.

public void setAge(int age) {
	if (age == null) {
    	    throw new NullPointerException("나이는 음수가 될 수 없습니다.");
    	}
	if (age < 0) {
    	    throw new IllegalArgumentException("나이는 음수가 될 수 없습니다.");
    	}
	this.age = age;
}

 

4. ConcurrentModificationException

단일 스레드 환경에서 적합한 동작을, 멀티 스레드에서 동작하려고 할 때 발생하는 예외이다. 동시에 수정을 하는 상황을 확실히 검출할 수 있는 방법은 없기 때문에 ConcurrentModificationException은 문제가 생길 가능성을 알려주는 역할을 한다.

아래 코드를 실행시키면 ConcurrentModificationException이 발생하는 것을 알 수 있다.

void noneFunctionalStream() {
      List<Integer> matched = new ArrayList<>();
      List<Integer> elements = new ArrayList<>();
      for (int i = 0; i < 100; i++) {
          elements.add(i);
      }
      elements.parallelStream()
              .forEach(e -> {
                  if (e >= 50) {
                      System.out.println(Thread.currentThread().getId() 
														+ " " + matched);
                      matched.add(e);
                  }
              });
      System.out.println(matched.size());
  }

 

5. UnsupportedOperationException

클라이언트가 요청한 동작을 대상 객체가 지원하지 않을 때 발생하는 예외이다. 예를 들어 add나 remove를 허용하지 않는 List 구현체에 해당 요청을 보내는 경우 발생한다.

List<Crew> crews = List.of(new Crew("gray"), new Crew("echo"));

crews.add(new Crew("hoi");
crews.remove(new Crew("gray");

표준 예외들의 상위 타입을 사용하면 안될까?

앞서 살펴본 표준 예외들은 상위 타입 예외를 가지고 있다. Exception, RuntimeException, Throwable, Error와 같은 상위 타입을 가지고 있는데 이러한 상위 타입을 직접 재사용하는 것은 좋지 않은 방법이다. 좋지 않은 방법이라기 보다는 하지 않는 것이 좋다.

왜냐하면, 여러 성격의 예외들을 포괄하는 클래스들이기 때문에 안정적으로 테스트할 수 없다.

 

예를들어 우리가 테스트 코드를 작성할 때, 적절한 요구사항에 맞는 예외를 발생시키는 테스트를 한다고 하자. 이 때 상위 타입의 예외로 검증하게 되면, 우리가 원하는 특정한 예외가 발생했는지 알 수 없다. 그러므로 Exception, RuntimeException, Throwable, Error와 같은 상위 타입은 추상 클래스라고 생각하는 것이 좋다. 

 

가장 바람직한 방법은, 상황에 부합하는 표준 예외를 재사용하고 이 때 자바 API 문서를 참고해 그 예외가 어떤 상황에서 던져지는 것이 적절한지 이중으로 확인하는 것이 좋다.


 

커스텀 예외

커스텀 예외는 예외 이름 자체가 정보를 전달할 수 있다. NoSuchElementException 보다는 CarNotFoundException이 더 명확하다.

또한 더 상세하게 예외 정보를 제공할 수 있고, 예외에 필요한 메시지, 전달할 정보의 데이터 등등을 한 곳에서 관리가 가능하다.

하지만, 일일이 예외 클래스를 만들다보면 지나치게 커스텀 예외가 많아질 수 있다. 해당 디렉토리와 클래스를 관리하는 것 역시 일이기 때문에 적절히 사용하는 것이 좋다.

public class CarNotFoundException extends RuntimeException {
	public CarNotFoundException(String id) {
		super("car with " + id + " is not found.")
	}
}

 

 

 

댓글