실패 원자적 (failure-atomic) : 호출된 메서드가 실패하더라도 해당 객체는 메서드 호출 전 상태를 유지해야 한다.
메서드를 실패 원자적으로 만드는 방법
1. 불변 객체로 설계
- 불변 객체는 태생적으로 실패 원자적이다.
- 메서드가 실패하면 새로운 객체가 만들어지지는 않을 수 있으나 기존 객체가 불안정한 상태에 빠지는 일은 없다.
2. 작업 수행에 앞서 매개변수의 유효성 검사
- 가변 객체의 메서드를 실패 원자적으로 만드는 가장 흔한 방법
- 객체의 내부 상태를 변경하기 전에 잠재적 예외의 가능성 대부분을 걸러낼 수 있다.
- 계산을 수행해 보기 전에는 인수의 유효성을 검사해 볼 수 없다면, 실패할 가능성이 있는 모든 코드를 객체의 상태를 바꾸는 코드보다 앞에 배치
ex) TreeMap
- TreeMap은 내부에서 RB 트리를 통해 정렬한다.
- 따라서 TreeMap에 원소를 추가하려면 비교할 수 있는 타입이어야 한다.
- 그렇지 않은 타입이 추가되려하면 트리를 변경하기 전에 해당 원소가 들어갈 위치를 찾는 과정에서 ClassCastException을 던진다
3. 객체의 임시 복사본에서 작업을 수행한 다음, 작업이 성공적으로 완료되면 원래 객체와 교체한다.
- 데이터를 임시 자료구조에 저장해 작업하는 게 더 빠를 때 적용하기 좋은 방식이다.
ex) 정렬 메서드
- 어떤 정렬 메서드에서는 정렬을 수행하기 전에 입력 리스트의 원소들을 배열로 옮겨 담는다.
- 이는 정렬에 실패하더라도 입력 리스트는 변하지 않는 효과를 얻게 된다.
4. 작업 도중 발생하는 실패를 가로채는 복구 코드를 작성하여 작업 전 상태로 되돌린다.
- 주로 디스크 기반의 내구성(durability)을 보장해야 하는 자료구조에 쓰이는데, 자주 쓰이는 방법은 아니다.
주의할 점
1. 실패 원자성은 일반적으로 권장되는 덕목이지만 항상 달성할 수 있는 것은 아니다.
- 두 스레드가 동기화 없이 같은 객체를 동시에 수정한다면 그 객체의 일관성이 깨질 수 있다.
- 따라서 ConcurrentModificationException을 잡아냈다고 해서 그 객체가 여전히 쓸 수 있는 상태라고 가정해서는 안된다.
ConcurrentModificationException
Java에서 컬렉션을 수정하는 도중에 다른 스레드가 수정을 시도할 때 발생할 수 있는 예외
2. Error는 복구할 수 없으므로 AssertionError에 대해서는 실패 원자적으로 만들려는 시도조차 할 필요가 없다.
AssertionError
주로 디버깅 목적으로 사용되는 예외 클래스로, 특정 조건이 참이어야 하는데 그렇지 않은 경우 발생.
예를 들어, 어떤 함수가 특정 전제 조건을 만족해야 하는데 그렇지 않을 경우 AssertionError를 발생시키는 방식으로 사용될 수 있다.
3. 실패 원자적으로 만들 수 있더라도 항상 그리해야 하는 것은 아니다.
- 실패 원자성을 달성하기 위한 비용이나 복잡도가 아주 큰 연산도 있다.
4. 메서드 명세에 기술한 예외라면 예외가 발생하더라도 객체의 상태는 메서드 호출 전과 똑같이 유지돼야 한다는 것이 기본 규칙이다. 이 규칙을 지키지 못한다면 실패 시의 객체 상태를 API 설명에 명시해야 한다.
'Java > Effective Java' 카테고리의 다른 글
[Effective Java] Item 78. 공유 중인 가변 데이터는 동기화해 사용하라 (0) | 2023.08.13 |
---|---|
[Effective Java] Item 77. 예외를 무시하지 말라 (0) | 2023.08.13 |
[Effective Java] Item 75. 예외의 상세 메시지에 실패 관련 정보를 담으라 (0) | 2023.08.12 |
[Effective Java] Item 74. 메서드가 던지는 모든 예외를 문서화하라 (0) | 2023.08.11 |
[Effective Java] Item 73. 추상화 수준에 맞는 예외를 던지라 (0) | 2023.08.11 |