[아이템 76] 가능한 한 실패 원자적으로 만들라
메서드가 실패하더라도 해당 객체는 메서드 호출 전 상태(정상적으로 사용할 수 있는 상태)를 유지해야 한다.
이러한 특성을 실패 원자적(failure-atomic)이라고 한다.
실패 원자적으로 만드는 방법
✅ 메서드를 불변 객체로 설계하기
메서드가 실패하면 새로운 객체가 만들어지지 않고,
기존 객체가 불안정한 상태에 빠지는 일도 결코 없다.
불변 객체의 상태는 생성 시점에 고정되어 절대 변하지 않기 때문이다.
✅ 가변 객체의 메서드의 작업 수행 전에 매개변수의 유효성을 검사하기
객체의 내부 상태를 변경하기 전에 잠재적 예외의 가능성 대부분을 걸러낼 수 있다.
✅ 객체의 임시 복사본에서 작업을 수행한 다음, 작업 성공 후 원래 객체와 교환하기
데이터를 임시 자료구조에 저장해 작업하는 게 더 빠를 때 적용하기 좋은 방식이다.
ex)
어떤 정렬 메서드에서 정렬을 수행하기 전에 입력 리스트의 원소들을 배열로 옮겨 담는다.
배열을 사용하면 정렬 알고리즘의 반복문에서 원소들에 훨씬 빠르게 접근할 수 있기 때문
(정렬에 실패하더라도 입력 리스트는 변하지 않는다.)
✅ 작업 도중 발생하는 실패를 가로채는 복구 코드를 작성해 작업 전 상태로 되돌리기
주로 디스크 기반의 내구성을 보장해야 하는 자료구조에 쓰인다.(자주 쓰이는 방법은 아니다.)
실패 원자성은 권장되는 덕목이지만 항상 달성할 수 있는 것은 아니다.
ex)
두 스레드가 동기화 없이 같은 객체를 동시에 수정한다면 그 객체의 일관성이 깨질 수 있다.
ConcurrentModificationException을 잡아냈다고 해서 그 객체가 여전히 쓸 수 있는 상태라고 가정할 수 없다.
💡ConcurrentModificationException : 보통 리스트나 Map 등, Iterable 객체를 순회하면서 요소를 삭제하거나 변경을 할 때 발생
또한 Error는 복구할 수 없기 때문에 AssertionError에 대해 실패 원자적으로 만들 필요가 없다.
정리
메서드 명세에 기술한 예외라면 예외 발생 시 객체의 상태는 메서드 호출 전과 똑같이 유지돼야 한다는 것이 기본 규칙!그렇지 못하다면 실패 시 객체 상태를 API 설명에 명시하자!