Item 76 - 가능한 한 실패 원자적으로 만들라
실패 원자성(Failure Atomicity)
호출된 메소드가 실패하더라도 해당 객체는 메서드 호출 전 상태를 유지해야 한다.
- 위와 같은 특성을 실패 원자성이라고 한다.
- 실패 원자성은 시스템의 안정성, 성능, 신뢰성을 향상시키는데 필수적이다.
메서드 실패 원자적으로 만드는 방법
방법 1 : 불변 객체로 설계하기 - item17
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class String {
// ...
public String substring(int beginIndex, int endIndex) {
int length = length();
checkBoundsBeginEnd(beginIndex, endIndex, length);
int subLen = endIndex - beginIndex;
if (beginIndex == 0 && endIndex == length) {
return this;
}
return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen)
: StringUTF16.newString(value, beginIndex, subLen);
}
// ...
}
- 불변 객체의 상태는 생성 이후 절대 변하지 않으므로 자연스럽게 실패 원자적이다.
- 즉, 기존 객체가 불안정한 상태에 빠지는 일은 생기지 않는다.
방법 2 : 매개변수 유효성 검사 - item 49
작업 수행에 앞서 매개변수의 유효성을 체크한다.
1
2
3
4
5
6
7
8
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // 다 쓴 참조 해제
return result;
}
- 객체 내부 상태가 변하기 전에 잠재적 예외 가능성을 대부분 걸러낸다.
1
2
3
4
5
public void add(String key, Object value) {
if (!(value instanceof Integer))
throw new ClassCastException(value.toString());
map.put(key, (Integer) value);
}
- 실패할 가능성이 있는 모든 코드를, 객체의 상태를 바꾸는 코드보다 앞에 배치하는 방법
- TreeMap에 원소를 추가하기 전에 정렬을 위한 ‘어떤 기준‘에 적합한지 검사하여, 엉뚱한 타입의 원소라면 ClassCastException을 던지게 한다.
방법 3 : 임시 구조 사용
객체의 임시 복사본에서 작업한 이후, 작업이 성공적으로 완료되면 원래 객체와 교체한다.
1
2
3
4
5
6
7
8
9
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray(); // 배열로 변환
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
} // 성공적으로 완료되면 원래 객체와 교체한다
}
- 데이터를 임시 자료구조에 저장해 작업하는게 더 빠르다면 적용하기 좋은 방식이다.
- 정렬 시 배열을 사용하면 참조 지역성이 높아져 반복문에서의 원소 접근이 훨씬 빨라진다.
- 정렬에 실패하더라도 입력 스트림은 변하지 않는 효과도 얻을 수 있다.
방법 4 : 복구 코드
- 작업 도중 발생하는 실패를 가로채는 복구코드를 작성하여 작업 전 상태로 되돌리는 방법
- 주로 디스크 기반의 내구성(durability)을 보장해야 하는 자료구조에 쓰인다.
- 자주 쓰이는 방법은 아니다.
실패 원자성 보장이 힘든 경우 🤔
실패 원자성은 권장되는 덕목이지만 항상 달성할 수 있는 것은 아니다.
또한 실패 원자적으로 만들 수 있다고 하더라도 항상 그리해야 하는 건 아니다.
예시 1 : ConcurrentModificationException
- 두 스레드가 동기화 없이 같은 같은 객체를 동시에 수정하려고 할 때 발생한다.
- 이 경우,
ConcurrentModificationException
을 잡아냈다고 해서 그 객체가 여전히 쓸 수 있는 상태라고 가정하면 안된다.
예시 2 : Error
, AssertionError
- 시스템 레벨의 문제는 일반적인 방법으로 복구가 불가능하다.
- 따라서 실패 원자성을 보장하려는 시도는 의미가 없다.
실패 원자성을 지키지 못한다면, 실패 시 객체 상태를 API 설명에 명시해야 한다.
This post is licensed under CC BY 4.0 by the author.