호출된 메서드가 실패하더라도 해당 객체는 메서드 호출 전 상태를 유지하는 특성을 실패 원자적(failure-atomic)이라고 합니다.
불변 객체는 태생적으로 실패 원자적이므로 메서드가 실패하면 새로운 객체가 만들어지지 않을 수는 있으나 기존 객체가 불안정한 상태에 빠지는 일은 결코 없습니다.
객체의 내부 상태를 변경하기 전에 잠재적 예외의 가능성을 대부분 걸러낼 수 있는 방법입니다. 또한 추상화 수준에 어울리는 에러를 던지도록 할 수 있습니다. 혹은 실패할 가능성이 있는 모든 코드를 객체의 상태를 바꾸는 코드보다 앞에 배치하는 방법도 있습니다.
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // 다 쓴 참조 해제
return result;
}
- 작업 수행에 앞서 매개변수 유효성을 검사하는 것과 비슷한 취지의 방법입니다. 계산을 수행해보기 전에 인수의 유효성을 검사해볼 수 없을 때 앞서의 방식에 덧붙여 쓸 수 있는 기법입니다.
- 예를 들어 TreeMap을 생각해봅시다. TreeMap은 원소들을 어떤 기준으로 정렬하고, 따라서 TreeMap에 원소를 추가하려면 원소가 TreeMap에 기준에 따라 비교할 수 있는 타입이어야 합니다. 그래서 해당 TreeMap의 원소타입에 맞지 않은 엉뚱한 타입의 원소를 추가하면 해당 원소가 들어갈 위치를 찾는 과정에서
ClassCastException
을 던질 것이고 TreeMap의 상태는 변하지 않을 것입니다.
데이터를 임시 자료구조에 저장해 작업하는 게 더 빠를 때 적용하기 좋은 방법입니다. 예를 들어 배열을 사용하면 정렬 알고리즘의 반복문에서 원소들에 훨씬 빠르게 접근할 수 있기 때문에 어떤 정렬 메서드에서 정렬을 수행하기 전에 입력 리스트의 원소를 배열로 옮겨 담습니다. 물론 이는 성능을 높이고자한 결정이지만, 혹여 정렬에 실패해도 입력 리스트는 변하지 않는 효과를 얻을 수 있게 됩니다.
주로 (디스크 기반의) 내구성(durability)를 보장해야 하는 자료구조에 쓰이는데, 자주 사용되는 방법은 아닙니다.
권장되는 덕목이지만 항상 달성 가능하지는 않습니다. 두 스레드가 동기화 없이 같은 객체를 동시에 수정한다면 그 객체의 일관성이 깨질 수 있습니다. 따라서 예외를 던졌다고 해서 그 객체가 여전히 사용할 수 있는 상태라고 가정해서는 안됩니다.
실패 원자성을 달성하기 위한 비용이나 복잡도가 아주 큰 연산도 있기 때문에 실패 원자적으로 만들 수 있더라도 항상 그렇게 해야하는 건 아닙니다. 그래도 문제가 무엇인지 알고 나면 실패 원자성을 꽁짜로 얻을 수 있는 경우가 더 많습니다.