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

예외를 잡지 못해 프로그램이 실패하면 자바 시스템은 그 예외의 스택 추적(stack trace)정보를 자동으로 출력한다. 스택 추적은 예외 객체의 toString 메서드를 호출해 얻는 문자열로, 보통 예외 클래스 이름 뒤에 상세 메시지가 붙는 형태다. 이러한 정보가 실패 원인을 분석해야 하는 프로그래머들의 유일한 정보가 되기엔 정보가 부족하다. 따라서 사후 분석을 위해 실패 순간의 상황을 정확히 포착해 예외의 상세 메시지에 담아야 한다.

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

예를 들어 IndexOutOfBoundsException의 상세 메시지는 범위의 최솟값과 최댓값, 그리고 그 범위를 벗어났다는 인덱스의 값을 담아야 한다.

- 문서와 소스코드 정보

관련 데이터를 모두 담아야 하지만 장황할 필요는 없고, 보안과 관련된 정보(ex) 비밀번호, 암호 키)는 담지 말아야 한다. 예를 들자면 스택 추적에는 예외가 발생한 파일 이름과 줄 번호는 물론 스택에서 호출한 다른 메서드들의 파일 이름과 줄 번호까지 정확히 기록되어 있는 게 보통이다. 그러니 문서와 소스코드에서 얻을 수 있는 정보는 길게 늘어놔봐야 군더더기가 될 뿐이다.

예외의 상세 메시지와 최종 사용자에게 보여줄 오류 메시지를 혼동해서는 안 된다. 최종 사용자에게는 친절한 안내 메시지를, 예외 메시지는 가독성보다는 담긴 내용이 중요하다.

실패를 적절히 포착하려면 필요한 정보를 예외 생성자에서 모두 받아서 상세 메시지까지 미리 생성해놓는 방법도 괜찮다. 예를 들어 현재의 IndexOutOfBoundsException 생성자는 String을 받지만, 다음과 같이 구현했어도 좋았을 것이다.

/**
 * IndexOutOfBoundsException을 생성한다.
 *
 * @param lowerBound 인덱스의 최솟값
 * @param upperBound 인덱스의 최댓값 + 1
 * @param index 인덱스의 실젯값
 */
public IndexOutOfBoundsException(int lowerBound, int upperBound, int index) {
   // 실패를 포착하는 상세 메시지를 생성한다.
   super(String.format("최솟값: %d, 최댓값: %d, 인덱스: %d", lowerBound, upperBound, index));
   
   // 프로그램에서 이용할 수 있도록 실패 정보를 저장해둔다.
   this.lowerBound = lowerBound;
   this.upperBoudn = upperBound;
   this.index = index;
}

이렇게 해두면 프로그래머가 던지는 예외는 자연스럽게 실패를 더 잘 포착한다.

예외는 실패와 관련한 정보를 얻을 수 있는 접근자 메서드(lowerBound, upperBound, index)를 적절히 제공하는 것이 좋다. 포착한 실패 정보는 예외 상황을 복구하는 데 유용할 수 있으므로 접근자 메서드는 비검사 예외보다는 검사 예외에서 더 빛을 발한다.


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

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

- 메서드를 실패 원자적으로 만드는 방법

예시를 살펴보자.

```java
public Object pop() {
    if (size == 0) // size값 확인하여 0이면 예외를 던진다.
        throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null; // 다 쓴 참조 해제
    return result;
}
```

위와 비슷한 취지로 실패할 가능성이 있는 모든 코드를 객체의 상태를 바꾸는 코드보다 앞에 배치하는 방법이 있다.
로직을 수행하기 전에 인수의 유효성을 검사하기 어려울 때 사용할 수 있다.
`TreeMap`을 예로 들면 잘못된 타입의 원소를 추가할 때 트리를 변경하기 앞서 해당 원소가 들어갈 위치를 찾는 과정에서 `ClassCastException`을 던진다.