Home [Effective Java] Item76. 가능한 한 실패 원자적으로 만들라!
Post
Cancel

[Effective Java] Item76. 가능한 한 실패 원자적으로 만들라!

Item76. 가능한 한 실패 원자적으로 만들라!

Intro

  • 작업 도중 예외가 발생해도 그 객체는 여전히 정상적으로 사용할 수 있는 상태라면 좋을 것이다.

  • 그러려면, 호출된 메서드가 실패하더라도 해당 객체는 메서드 호출 전 상태를 유지해야 한다.

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

불변 객체로 설계

  • 불변 객체로 설계하는 간단한 방법이 있다.
    • 메서드가 실패하면 새로운 객체가 만들어지지 않을 수는 있다. 하지만 기존 객체가 불안정해지는 일은 없다.
    • 이유? 불변 객체의 상태는 생성 시점에 고정되어 절대 변하지 않기 때문

가변 객체의 경우

  • 작업 수행에 앞서 매개변수의 유효성을 검사하는 것이다.
    • 객체의 내부 상태를 변경하기 전에 잠재적 예외의 가능성 대부분을 걸러낼 수 있다.
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;
}
  • 이 메서드는 처음의 if 문에서 size를 확인하고, 0이면 예외를 던진다.
  • 사실상 이러한 예외를 던지는 부분을 제거하더라도 여전히 예외를 던지기는 한다. 이때 던지는 예외는 ArrayIndexOutOfBoundException 이다.
    • 다만 이렇게 되면, 이후 작업에 대해 ArrayIndexOutOfBoundException을 던지게 되고, 다음 번 호출에도 size가 음수가 되어 같은 예외를 던지게 된다. 그러면 그 객체는 더 이상 제 역할을 못하게 된다.
    • 또, ArrayIndexOutOfBoundException 예외는 추상화 수준이 상황에 어울리지 않다고 볼 수 있다.

실패할 가능성이 있는 모든 코드를, 객체의 상태를 바꾸는 코드보다 앞에 배치하자.

  • 계산 수행 전, 인수의 유효성을 검사해볼 수 없을 때 앞서의 방식에 덧붙여 쓸 수 있는 기법이다.
  • 예를 들어 TreeMap에 원소를 추가하는 경우, 그 원소는 TreeMap의 기준에 따라 비교 가능한 타입이어야 한다. 엉뚱한 타입을 추가하려 시도한다면, 해당 원소가 들어갈 위치를 찾는 과정에서 ClassCastException을 던질 것이다.

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

  • 데이터를 임시 자료구조에 저장해 작업하는 게 더 빠를 때 적용하기 좋은 방식이다.
  • 예를 들어 리스트 정렬 시, 정렬의 성능 개선을 위해 배열에 옮겨 작업을 수행한다. 성능을 위한 결정이었지만, 이렇게 함으로써 정렬 작업 실패 시에도 입력 리스트의 상태는 변하지 않는 효과를 덤으로 얻게 되었다.

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

  • 주로 (디스크 기반의) 내구성(durability)을 보장해야 하는 자료구조에 쓰인다. 자주 쓰이진 않는다.

꼭 실패 원자적이어야 하는가?

위 방법이 항상 실패 원사성을 가져다 주는가?

  • 실패 원자성은 일반적으로 권장되는 덕목이다. 하지만 항상 달성할 수 있는 것은 아니다.
  • 예를 들어 두 스레드가 동기화 없이 같은 객체를 동시에 수정하는 경우, 그 객체의 일관성이 깨질 수 있다.
    • 따라서 ConcurrentModificationException을 잡아냈다고 해서 그 객체가 여전히 쓸 수 있는 상태라 가정하면 안 된다.
  • Error는 복구할 수 없다. 그러므로 AssertionError에 대해서는 실패 원자적으로 만들려는 시도조차 필요 없다.

실패 원자적으로 설계하는 것이 필수적인가?

  • 실패 원자성을 달성하기 위한 비용이나 복잡도가 아주 큰 연산이라면, 꼭 실패 원자적으로 만들 필요는 없다.
  • 그래도 문제를 파악하다 보면 실패 원자성을 공짜로 얻을 수 있는 경우가 더 많다.
  • 메서드 명세에 기술한 예외의 경우, 설혹 예외가 발생하더라도 객체 상태는 메서드 호출 전과 똑같이 유지돼야 한다.
    • 이것을 지키지 못한다면 실패 시의 객체 상태를 API 설명에 명시해야 한다. (하지만 지금의 API 문서 상당 부분이 잘 지키지 않고 있다.)
This post is licensed under younghwani by the author.

[Effective Java] Item75. 예외의 상세 메시지에 실패 관련 정보를 담으라!

[Effective Java] Item77. 예외를 무시하지 말라!

Comments powered by Disqus.