728x90
아이템 76. 가능한 한 실패 원자적으로 만들라
실패 원자적(failure-atomic)?
- 호출된 메소드가 실패하더라도 해당 객체는 메서드 호출 전 상태를 유지해야 한다는 특성
어떻게 하면 실패 원자적 특성을 가진 메서드를 만들수 있을까?
- 아주 간단한 방법은 불변 객체(아이템 17)로 만드는 것이다.
- 메서드가 실패하면 새로운 객체가 만들어 지지 않을 수 있으나, 기존 객체가 불안정한 상태에 빠질일은 없다.
- 왜냐하면 생성 시점에 고정되어 절대 변하지 않기 때문이다.
그럼 가변객체는?
- 가장 흔한 방법은 작업 수행에 앞서 매개변수를 검사하는 것(아이템 49)이다.
- 쉽게 말하면 객체 내부 상태가 변하기 전에 잠재적 예외 가능성을 걸러내라는 의미이다.
- 간단한 Stack 코드를 보자.
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
....
}
- 현재 pop 메서드를 실행하기 전에 Stack의 Size가 0이면 예외를 던지는걸 확인할 수 있다.
- 사실 이러한 Validation이 없어도 스택이 비었다면 예외를 던지게 되긴 한다. 그러나 그 이후 모든 작업에 대해 ArrayIndexOutOfBoundException을 던지게 되는 불상사가 발생하게 된다. 즉, 그 객체는 스택이 비어 있을때 호출 한 이후 정상적인 객체의 역할을 못하게 되는 것이다.
- 아울러 ArrayIndexOutOfBoundException은 추상화 수준이 상황에 어울리지 않는다고도 볼 수 있다.(아이템 73)
다른 방법은 또 없을까?
실패할 가능성이 있는 모든 코드를 객체의 상태를 바꾸는 코드보다 앞으로 두자.
- 즉, 계산을 수행하보기 전에 인수의 유효성을 검사해볼 수 없을 때 앞서 방식에 덧붙여 쓸수 있는 방식이라고 생각하며 된다.
- 이게 무슨말인가? 예시를 살펴보자.
- TreeMap은 원소들을 어떤 기준에 맞춰서 정렬한다.
- 이때 그 원소는 기준에 따라 비교할 수 있는 타입이어야 한다. 엉뚱한 타입이 들어오면 Tree를 변경하기 전에 ClassCastException을 던지게 될 것이다.
객체의 임시 복사본에서 작업을 한 이후 작업이 완료 되면 원래 객체와 교체하자.
- 데이터를 임시 자료구조에 저장해 작업하는게 더 빠르다면 적용하기 좋은 방식이다.
- 예를들어 어떤 정렬 메서드에서는 정렬을 수행하기 전에 입력 리스트의 원소들을 배열로 옮겨 담는다.
- 배열을 사용하면 정렬 알고리즘의 반복문에서 원소들에 빠르게 접근가능하기 때문이다.
- 이에 대한 이점은 성능도 있지만, 정렬에 실패해도 입력 리스트는 변하지 않는 효과를 덤으로 얻게 된다.
작업 도중 발생하는 실패를 가로채고 복구 코드를 작성하여 작업 전 상태로 되돌리는 방법
- 주로 (디스크 기반내) 내구성을 보장해야 하는 자료구조에 쓰이는데, 자주 쓰이지는 않는다.
위 방법을 쓰면 항상 원자성을 보장할 수 있는가?
- 아쉽게도 항상 달성할 수 있는 것은 아니다.
- 자바는 멀티 스레드 방식으로 동작하기 때문에 두 스레드가 동기화 없이 같은 객체를 동시에 수정하면 일관성이 깨질 확률이 높다.
- 따라서 이럴 경우에는 ConcurrentModificationException을 잡아냈다고 해서, 그 객체에 여전히 사용할 수 있다고 가정하면 안된다.
- 또한, Error는 복구할 수 없으므로 AssertionError에 대해 실패 원자적으로 만들 필요는 없다.
그럼 최대한 실패 원자적으로 만들어야 하는가?
- 최대한 만들어야 하지만, 항상 그리 해야 하는 것은 아니라고 생각한다.
- 그 이유는 실패 원자적으로 만들기 위해 비용이나 복잡도가 굉장히 높을 수 있기 때문이다.
- 아울러 문제를 파악하면 실패 원자성을 꽁짜로 얻을수 있는 경우가 있다.
- 그러나 메서드 명세에 기술한 예외라면 혹시 예외가 발생하더라도 객체의 상태는 메서드 호출 전과 똑같은 상태가 유지되어야 한다는 것이 기본 규칙이다. 혹시라도 이 규칙을 못지킨다면 API 문서에 명시해야 한다.
- 그러나 아쉽게도 이 부분이 잘 지켜지지 않는게 현실이다.
결론
- 실패 원자적은 호출된 메소드가 실패하더라도 해당 객체는 메서드 호출 전 상태를 유지해야 한다는 특성이다.
- 이 특성을 갖기 위한 방법은 아래와 같다.
- 불변객체
- 실패할 가능성이 있는 코드를 앞으로 두자
- 임시 복사본을 만들어 성공하면 원래 객체와 임시 복사본을 교체하자
- 작업도중 실패하는 코드를 가로채고 복구 코드를 작성하자
- 그러나 원자성을 보장할 수 없을 때가 있다.
- 두 스레드가 동기화 없이 같은 객체를 동시에 수정하는 경우
- Error
- 원자성을 최대한 보장해줘야 하지만, 항상 그러한 것은 아니다. 비용이나 복잡성이 높다면 포기하는 것을 추천한다.
- 아울러 그러한 부분은 꼭 API 문서에 적어두자.
728x90
728x90
'Book > 이펙티브 자바 3판' 카테고리의 다른 글
아이템54. null이 아닌, 빈 컬렉션이나 배열을 반환하라 (0) | 2021.03.19 |
---|---|
아이템 53. 가변인수는 신중히 사용하라 (0) | 2021.03.19 |
아이템45. 스트림을 주의해서 사용하라 (0) | 2021.03.19 |
아이템35. ordinal 메서드 대신 인스턴스 필드를 사용하라 (0) | 2021.03.19 |
아이템14. Comparable을 구현할지 고려하라 (0) | 2021.03.19 |