본문 바로가기
Language/Effective java

예외

by y.j 2022. 8. 19.
728x90

메서드가 던지는 모든 예외를 문서화하라.

메서드가 던지는 예외는 그 메서드를 오바로 사용하는 데 아주 중요한 정보다. 따라서 문서화하는데 충분한 시간을 쏟아야 한다. 

 

검사 예외는 항상 따로따로 선언하고, 각 예외가 발생하는 상황을 자바독의 @throws 태그를 사용하여 정확히 문서화하자.

  • 공통 상위 클래스 하나로 뭉뚱그려 선언하는 일은 삼가자( Exception, Throwable ). 사용자에게 어떤 예외인지 정확히 전달이 안될 뿐더러 다른 예외들을 삼켜버릴 수 있다. main메서드는 JVM만이 호출하므로 Exception을 던지도록 선언해도 괜찮다.
  • 비검사 예외들을 문서화하여 메서드를 작성할시 전제조건을 명시하자. 

 

메서드가 던질 수 있는 예외를 각각 @throws 태그로 문서화하되, 비검사 예외는 메서드 선언 throws 목록에 넣지 말자.

  • 검사냐 비검사냐를 정확하게 명시해주는 것이 좋다.
  • 주석 @throw는 비검사를 의미하고
  • 메서드에 throw를 통해 정의하는 것은 검사를 의미한다.

 

한 클래스에 정의된 많은 메서드가 같은 이유로 같은 예외를 던진다면 그 예외를 (각각의 메서드가 아닌) 클래스 설명에 추가하는 방법도 있다.

  • NullPointerException이 가장 흔하다.
  • "이 클래스의 모든 메서드는 인수로 null이 넘어오면 NullPointerException을 던진다"라고 적는다.

 

예외의 상세 메시지에 실패 관련 정보를 담으라

예외를 잡지 못해 프로그램이 실패하면 자바 시스템은 그 예외의 스택 추적(stak trace) 정보를 자동으로 출력한다. 스택 추적은 예외 객체의 toString 메서드를 호출해 얻는 문자열로, 보통은 예외의 클래스 이름 뒤에 상세 페이지가 붙는 형태다. toString에 가능한한 많은 실패원인을 담는 것이 중요하다.

 

실패 순간을 포착하려면 발생한 예외에 관여된 모든 매개변수와 필드의 값을 실패 메시지에 담아야 한다.

  • 예시로 IndexOutOfBoundsException의 상세페이지는 범위의 최솟값과 최댓값, 범위를 벗어난 인덱스의 값을 담아야 한다.
  • 관련 데이터를 모두 담아야 하지만 장황할 필요는 없다.
/**
 * IndexOutOfBoundsException을 생성한다.
 *
 * @param lowerBound 인덱스 최솟값
 * @param upperBound 인덱스 최댓값
 * @param index 인덱스의 실젯값
 */
public IndexOutOfBoundsException(int lowerBound, int upperBound, int index) {

    super(String.format(
            "최솟값: %d, 최댓값: %d, 인덱스: %d",
            lowerBound, upperBound, index0));

    this.lowerBound = lowerBound;
    this.upperBound = upperBound;
    this.index = index;
}

 

가능한 한 실패 원자적으로 만들라

호출된 메서드가 실패하더라도 해당 객체는 메서드 호출 전 상태를 유지하는 것을 원자적이라고 한다. 

 

객체를 원자적으로 만드는 방법

1. 불변객체를 만들기

  • 불변 객체는 태생적으로 원자적이다. 실패하면 새로운 객체가 생성되지 않으나 기존객체가 불안정한 상태에 빠지는 일은 결코 없다.
  • 아래예시는 size가 음수가 될 경우 예외를 던져 새로운 객체를 생성하지 않는다. 하지만 추상화 수준이 상황에 어울리지 않다고 볼 수 있다.
public Object pop() {
    if(size == 0)
        throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null;
    return result;
}

 

2. 예외가 일어날 모든 코드를 상태를 바꾸는 코드보다 앞에 배치하는 방법

  • TreeMap에서 원소들을 어떤 기준으로 정렬할 때 원소가 들어가기에 앞서 ClassCastException에러를 던지면 좋다.

 

3. 객체의 임시복사본에서 작업을 수행한 다음 작업이 성공적으로 완료되면 원래 객체와 교환하는 것이다.

 

4. 복구코드를 입력해 놓는다. 자주쓰이지는 않는다.

 

예외를 무시하지 말라

API설계자가 메서드 선언에 예외를 명시하는 까닭은 그 메서드를 사용할 때 적절한 조치를 취해달라고 얘기하는 것이므로 무시하지 말자. 

  • catch블록은 문제사항을 잘 대처하기 위해 존재하므로 비워두지 말자.
  • FileInputStream처럼 파일의 상태를 변경하지 않는다면 catch를 비워놔도 괜찮다.
try {
    ...
} catch (SomeException e) {
    
}

 

  • 예외를 무시하기로 했다면 catch 블록 안에 그렇게 결정한 이유를 남기고 예외 변수의 이름도 ignored로 바꿔놓도록 하자.
Future<Integer> f = exec.submit(planarMap::chromaticNumber);
int numColors = 4;
try {
    numColors = f.get(1L, TimeUnit.SECONDS);
} catch (TimeoutException | ExecutionException ignored) {
    
}

 

 

 

728x90

'Language > Effective java' 카테고리의 다른 글

동시성  (0) 2022.08.24
동시성  (0) 2022.08.22
예외  (0) 2022.08.18
예외  (0) 2022.08.17
일반적인 프로그래밍 원칙  (0) 2022.08.12

댓글