핵심 요약
- 실패 원자성은 메소드 호출이 실패하더라도 해당 객체의 상태를 변경하지 않아야 함을 나타낸다.
- 실패 원자성을 달성하기 위해 불변 객체 사용, 유효성 검사, 임시 구조 사용, 복구 코드 사용 등을 이용하자.
- 실패 원자성을 항상 보장하기는 어렵지만, 그렇지 않을 경우 API 문서에 명시해야 한다.
실패 원자성(Failure Atomicity)
✍ 호출된 메소드가 실패하더라도 해당 객체는 메소드 호출 전 상태를 유지해야 한다.
- 위와 같은 특성을 실패 원자성이라고 한다.
- 실패 원자성은 시스템의 안정성, 성능, 신뢰성을 향상시키는 데 필수적이다.
메소드를 실패 원자적으로 만드는 방법
방법 1 | 불변 객체 사용
: 불변 객체로 설계한다. (item 17)
- 불변 객체는 생성 이후 절대 변하지 않으므로 자연스럽게 실패 원자적이다.
- 즉, 기존 객체가 불안정한 상태로 절대 변질되지 않는다.
방법 2 | 유효성 검사
: 작업 수행에 앞서 매개변수의 유효성을 검사한다. (item 49)
- 객체의 내부 상태를 변경하기 전에 잠재적 예외의 가능성 대부분을 걸러낸다
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // 다 쓴 참조 해제
return result;
}
- ex. Spring @Valid
방법 3 | 가변 객체에서의 실패 원자성
: 실패할 가능성이 있는 모든 코드를, 객체의 상태를 바꾸는 코드보다 앞에 배치한다.
- 로직을 수행하기 전에 인수의 유효성을 검사하기 어려울 때 사용할 수 있다.
- ex. TreeMap
- 잘못된 타입의 원소를 추가하려 할 때, 그 원소를 트리에 추가하기 전에 해당 원소의 적절한 위치를 찾는 과정에서 ClassCastException이 발생한다.
- 즉, 트리의 구조나 상태가 변경되기 전에 이미 예외가 발생하여 TreeMap의 상태가 일관되게 유지된다.
방법 4 | 임시 구조 사용
: 객체의 임시 복사본에서 작업을 수행한 후에 성공적으로 완료되면 원래 객체와 교체한다.
- 데이터를 임시 자료구조에 저장하여 작업하는 것이 원본 데이터 구조에 직접 작업하는 것보다 효율적일 때 유용하다.
- ex. List 인터페이스 sort 메소드
- Arrays.sort 메소드 호출 중에 예외가 발생하더라도 원본 리스트는 유지된다.
- 또한, 배열을 사용하므로 반복문에서 원소들에 빠르게 접근할 수 있다. 따라서 성능 측면에서도 효율적이다.
// List 인터페이스 sort 메소드 예시
Object[] a = this.toArray(); // 현재 리스트의 모든 원소들을 객체 배열로 복사
Arrays.sort(a, (Comparator) c); // 배열의 원소들을 주어진 Comparator c를 사용하여 정렬
ListIterator<E> i = this.listIterator(); // 원본 리스트의 리스트 이터레이터를 생성
for (Object e : a) { // 이 부분은 정렬된 배열의 모든 원소들을 순회하며, 원본 리스트의 해당 위치에 그 원소들을 설정(교체)
방법 5 | 복구 코드 사용
: 작업 도중에 발생하는 실패를 가로채는 복구 코드를 작성하여 작업 전 상태로 되돌린다.
- 대부분의 애플리케이션은 복구 코드를 직접 작성하는 것보다 외부 라이브러리나 프레임워크의 기능을 활용한다.
- ex. Spring @Transactional
실패 원자성을 보장하기 어려운 경우
- 실제 개발 상황에서는 실패 원자성을 보장하기 어렵거나, 이를 보장하기 위한 비용이 너무 클 수 있다.
- 예시 1 - ConcurrentModificationException
- 다중 스레드 환경에서 한 스레드가 컬렉션을 수정하는 도중 다른 스레드가 동일한 컬렉션을 수정하려고 할 때 발생한다.
- 이런 경우에서는 에러가 발생하더라도 객체가 유지된다고 가정할 수 없으며, 락이나 동기화 같은 추가적인 도구가 필요하다.
- 예시 2 - Error, AssertionError
- 시스템 레벨의 문제는 일반적인 방법으로 복구가 불가능하므로, 이를 위해 실패 원자성을 보장하려는 시도는 의미가 없다.
✍ 실패 원자성을 지키지 못할 경우, 실패 시 객체 상태를 API 설명에 명시해야 한다.
'Language > Java' 카테고리의 다른 글
[effective java] 아이템 78. 공유 중인 가변 데이터는 동기화해 사용하라. (0) | 2023.08.13 |
---|---|
[effective java] 아이템 77. 예외를 무시하지 말라. (0) | 2023.08.12 |
[effective java] 아이템 75. 예외의 상세 메시지에 실패 관련 정보를 담으라. (0) | 2023.08.12 |
[effective java] 아이템 74. 메서드가 던지는 모든 예외를 문서화하라. (0) | 2023.08.12 |
[effective java] 아이템 73. 추상화 수준에 맞는 예외를 던지라. (0) | 2023.08.12 |