1. 실패 원자적 특성
- 호출된 메서드가 실패하더라도, 해당 객체는 메서드 호출 전 상태를 유지하는 성질
- Java 뿐만 아니라 MySQL과 같은 RDB에서도 특정 트랜젝션이 실패하면 DB의 상태는 트랜젝션이 발생하기 이전으로 되돌아가야 하는데(roll back) 이런 점 역시 실패 원자적 특성을 가진다라고 할 수 있다.
- 특히나 이런 성질은 오류 상태를 복구할 수 있을 때 던져지는 검사 예외에 대해 유용할 수 있다.
2. 실패 원자적으로 만드는 방법들
- 실패 원자적으로 메서드를 설계하는 방법은 다양하지만 가장 간단한 방법은 불변 객체로 설계하는 것이다.
- 불변 객체는 태생적으로 실패 원자적이다.
- 메서드가 실패하면 새로운 객체가 만들어지지는 않을 수 있으나, 기존 객체가 불안정한 상태에 빠지는 일은 절대 없다.
- 왜냐하면 불변 상태의 객체는 생성 시점에서 값이 고정되어 절대 변하지 않기 때문이다.
- 가변 객체의 메서드를 실패 원자적으로 만드는 가장 흔한 방법은 작업 수행에 앞서 매개변수의 유효성을 검사하는 것이다.(validation)
- 해당 방법은 객체의 내부 상태를 변경하기 전에 잠재적 예외의 가능성 대부분을 걸러낼 수 있는 방법이다.
public Object pop() {
if(size == 0) {
throw new EmptyStackException();
}
Object result = elements[--size];
elements[size] = null;
return result;
}
- 해당 코드는
size == 0
의 validation 을 거치지 않아도 여전히 예외를 던진다. 다만 size값이 음수가 되어 다음번 호출도 동일하게 실패하게 만들며 이때 던지는 ArrayIndexOutOfBoundsException
은 추상화 수준이 상황에 어울리지 않다고 볼 수 있다.
두 번째 방법
- 이와 비슷한 취지로 실패할 가능성이 있는 모든 코드를 객체의 상태를 바꾸는 코드보다 앞에 배치하는 방법도 있다.
- 실제 계산을 수행해보기 전에는 인수의 유효성을 검사해볼 수 없을 때 앞서의 방식에 덧붙여 쓸 수 있는 기법이다.
- 예를 들어
TreeMap
이 있다. TreeMap
은 원소를 어떤 기준(Comparator
를 제공받거나, Comparable
에 구현된 기준)으로 정렬하는데 원소를 삽입할 때 다른 타입의 원소를 추가하려고 할 시 트리를 변경하기 이전에 ClassCastException
을 던질 것이다.
세 번째 방법
- 세 번째 방법은 객체의 임시 복사본에서 작업을 수행한 다음, 작업이 성공적으로 수행되면 복사본의 값을 원래 객체의 값과 교체하는 것이다.
- 해당 방식은 데이터를 임시 자료구조에 저장해 작업하는 더 빠를 때 적용하기 좋은 방식이다.
- 예시로 어떤 정렬 메서드는 정렬을 수행하기 전에 입력 리스트의 원소들을 배열로 옮겨 담는다. 배열을 사용하면 정렬 알고리즘의 반복문에서 원소들에 훨씬 빠르게 접근할 수 있기 때문이다.
- 혹시나 정렬에 실패하더라도 입력 리스트는 변하지 않은 효과를 덤으로 얻게 된다.
마지막 방법
- 마지막 방법은 작업 도중 발생하는 실패를 가로채는 복구 코드를 작성하여 작업 전 상태로 되돌리는 방법이다.
- 주로 내구성을 보장해야하는 자료구조에 쓰이는 방식인데 자주 쓰이는 방법은 아니다.
실패 원자성의 유의점
- 실패 원자성은 일반적으로 권장되는 덕목이지만, 항상 달성할 수 있는 것은 아니다.