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

이진호·2022년 9월 9일
0

Effective Java

목록 보기
7/11
post-thumbnail

Item 76. 가능한 한 실패 원자적으로 만들라

호출된 메서드가 실패하더라도 해당 객체는 메서드 호출 전 상태를 유지해야 합니다. 이러한 특성을 실패 원자적(failure-atomic)이라고 합니다.

메서드를 실패 원자적으로 방법은 아래와 같습니다.

불변 객체(Item17)로 설계

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

매개변수의 유효성을 검사(Item 49)

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

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

위와 같이 메서드 처음의 if 문에서 size 값을 확인하여 예외를 던질 수 있습니다. 사실 이 부분이 없더라도 스택이 비어있다면 ArrayIndexOutOfBoundsException이 발생하긴 하지만, 해당 예외는 추상화 수준이 상황에 어울리지 않고(Item 73), size 값이 음수가 되어 다음번 호출도 실패하게 됩니다.

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

계산을 수행해보기 전에는 인수의 유효성을 검사해볼 수 없을 때 앞서의 방식에 덧붙여 쓸 수 있는 기법입니다.

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

데이터를 임시 자료구조에 저장해 작업하는 게 더 빠를 때 적용하기 좋은 방식입니다. 어떤 정렬 메서드에서는 정렬을 수행하기 전에 입력 리스트의 원소를 배열로 옮겨 담는데, 이와 같은 경우 혹시나 정렬에 실패하더라도 입력 리스트는 변하지 않는 효과를 얻게 됩니다.

작업 도중 발생하는 실패를 가로채는 복구 코드를 작성하여 작업 전 상태로 롤백

주로 (디스크 기반의) 내구성(durability)을 보장해야 하는 자료구조에 쓰이는데, 자주 쓰이는 방법은 아닙니다.

실패 원자성은 일반적으로 권장되는 덕목이지만 항상 달성할 수 있는 것은 아닙니다. 예를 들어 두 스레드가 동기화 없이 같은 객체를 동시에 수정한다면 그 객체의 일관성이 깨질 수 있습니다.(ex)ConcurrentModificationException이 발생한 경우)
또한 Error는 복구할 수 없으므로 AssertionError에 대해서는 실패 원자적으로 만들려는 시도를 할 필요가 없습니다.

실패 원자적으로 만들 수 있더라도 항상 그리 해야 하는것도 아닙니다. 실패 원자성을 달성하기 위한 비용이나 복잡도가 큰 경우도 있습니다. 그래도 일반적으론 문제 파악시 실패 원자성을 공짜로 얻을 수 있는 경우가 더 많습니다.

메서드 명세에 기술한 예외라면 예외가 발생하더라도 객체의 상태는 메서드 호출 전과 똑같이 유지되어야 한다는 것이 기본 규칙입니다. 이 규칙을 지키지 못한다면 실패 시 객체 상태를 API 설명에 명시해야 합니다.(아쉽게도 지금의 API 문서 상당 부분이 잘 지켜지지 않고 있습니다.)

출처

0개의 댓글