10장. 예외 - GOAL

예외를 제대로 활용한다면 프로그램의 가독성, 신뢰성, 유지보수성이 높아진다.
그러나 잘못 사용하면 반대의 효과만 나타난다.
예외를 효과적으로 활용하는 방법을 습득하자.

아이템76. 가능한 한 실패 원자적으로 만들라

실패 원자적(failure-atomic)

호출된 메서드가 실패하더라도 해당 객체는 메서드 호출 전 상태를 유지해야 한다.
이는 작업 도중 예외가 발생해도 그 객체는 여전히 정상적으로 사용할 수 있음을 의미한다.
만약 여기서 검사 예외를 던진다면 호출자가 오류 상태를 복구할 수 있을테니 특히 더 유용할 것이다.

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

1. 불변 객체로 설계

불변 객체는 태생적으로 실패 원자적이다.
메서드가 실패하면 새로운 객체가 만들어지지 않을 수 있으나, 기존 객체가 불안정한 상태에 빠질 일은 결코 없다.
불변객체의 상태는 생성 시점에 고정되어 절대 변하지 않기 때문이다.

2. 작업 수행에 앞서 매개변수의 유효성을 검사

객체의 내부 상태를 변경하기 전에 잠재적 예외의 가능성 대부분을 걸러낼 수 있는 방법이다.

public Object pop() {
    if(size == 0){
        throw new EmptyStackException();
}
    Object result = elements[--size];
    elements[size] = null // 다쓴 참조 해제
    return result;
}

사실 if 문이 없더라도 스택이 비었다면 예외를 던진다.
다만 size 의 값이 음수가 되어 다음 번 호출도 실패하게 만들며, 이때 던지는 ArrayIndexOutofBoundsException은 추상화 수준이 상황에 어울리지 않다고 볼수 있다.
덧붙여서 실패할 가능성이 있는 모든 코드를, 객체의 상태를 바꾸는 코드보다 앞에 배치하는 방법을 추가로 사용할 수 있다.

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

데이터를 임시 자료구조에 저장해 작업하는게 더 빠를 때 적용하기 좋은 방식
예를 들어 정렬 메서드에서 정렬을 수행하기 전에 입력 리스트의 원소들을 배열로 옮겨 담는다. 배열을 사용하면 정렬 알고리즘의 반복문에서 원소들에 훨씬 빠르게 접근할 수 있기 때문이다.
성능을 높여 줄 뿐만 아니라 , 혹시나 정렬에 실패하더라도 입력 리스트는 변하지 않는 효과를 얻을 수 있다.

4. 작업 도중 발생하는 실패를 가로채는 복구 코드를 작성하여 작업 전 상태로 되돌리는 방법

주로 디스크 기반의 내구성을 보장해야 하는 자료구조에 쓰이지만 자주 쓰이지는 않는다.

예외

실패 원자성을 항상 만족시킬 수는 없다.
예를 들어 두 스레드가 동기화 없이 같은 객체를 동시에 수정한다면 그 객체의 일관성이 깨질 수 있다.
또한 Error는 복구할 수 없으므로 AssertionError에 대해서는 실패 원자적으로 만들려는 시도조차 할 필요가 없다.

결론

실패 원자적으로 만들수 있더라도, 비용이나 복잡도가 큰 연산의 경우는 제외해야 한다.
또한 메서드 명세에 기술한 예외라면 혹시나 예외가 발생하더라도 객체의 상태는 메서드 호출 전과 똑같이 유지돼야 한다는 것이 기본규칙이다. 이를 지키지 못한다면 API 설명에 명시해야 하지만 잘 지켜지지 않고 있다.