item 69 : 예외는 진짜 예외 상황에만 사용하라
예외는 예외 상황에서만 써야 하지, 절대로 일상적인 제어 흐름용으로 쓰여서는 안된다. 또한, 이를 프로그래머에게 강요하는 API를 만들어서도 안된다.
1. 안 좋은 예시 : 제어 흐름용으로써의 사용
배열의 원소를 순회하는데, 무한루프를 돌다가 배열의 끝에 도달해 ArraylndexOutOfBoundsException 예외가 발생하면 끝을 내는 로직으로 작성한 코드이다. 직관적이지 않다는 점 하나만으로도 제어 흐름용으로 예외를 사용하면 안되는 이유는 충분하다.
표준적인 관용구
실제 예외를 사용한 쪽이 표준 관용구보다 2배 정도 느리다.
그런데 예외를 써서 루프를 종료한 이유는 도대체 뭘까?
잘못된 추론을 근거로 성능을 높여보려 한 것이다. JVM
은 배열에 접근할 때마다 경계를 넘지 않는지 검사하는데, 일반적인 반복문도 배열 경계에 도달하면 종료한다. 따라서 이 검사를 반복문에도 명시하면 같은 일이 중복되므로 하나를 생략한 것이다.
1) 문제점
예외는 예외 상황에 쓸 용도로 설계되었으므로 JVM 구현자 입장에서는 명 확한 검사만큼 빠르게 만들어야 할 동기가 약하다(최적화에 별로 신경쓰지 않을 가능성이 높다)
코드를 try-catch 블록 안에 넣으면 JVM이 적용할 수 있는 최적화가 제한된다.
배열을 순회하는 표준 관용구는 앞서 걱정한 중복 검사를 수행하지 않는 다. JVM이 알아서 최적화해 없애준다.
제대로 동작하지 않을 수도 있다. 반복문 안에 버그가 숨어 있다면 흐름 제어에 쓰인 예외가 이 버그를 숨겨 디버깅을 훨씬 어렵게 할 것이다.
표준 관용구였다면 버그의경우, 예외를 잡지 않고 스택 추적 정보를 남기고 해당 스레드를 즉각 종료시킬 것이다. 반면 예외
를 사용한 반복문은 버그 때문에 발생한 엉뚱한 예외를 정상적인 반복문 종료 상황으로 오해하고 넘어간다.
2) 즉, 말하고자 하는 것
예외는 오직 예외 상황 에서만 써야 한다.
절대로 일상적인 제어 흐름용으로 쓰여선 안 된다. 더 일반 화해 이야기하면 표준적이고 쉽게 이해되는 관용구를 사용하고, 성능 개선을 목적으로 과하게 머리를 쓴 기법은 자제하라.
2. API 설계 원칙: 상태 검사 메서드와 예외의 올바른 사용
이 원칙은 특히 상태 의존적인 메서드를 제공하는 클래스에서 매우 중요하다. 상태 의존적 메서드는 특정 상태에서만 호출이 가능하며, 이를 안전하게 호출할 수 있는지 확인하는 상태 검사 메서드를 반드시 함께 제공해야 한다. 예를 들어, 자바의 Iterator 인터페이스에서 next()는 상태 의존적 메서드이고, hasNext()는 이를 안전하게 호출할 수 있는지를 검사하는 상태 검사 메서드이다.
1) Iterator 예시
Iterator 인터페이스를 사용할 때, 안전한 반복을 위해 다음과 같은 표준적인 반복문을 사용한다:
여기서 hasNext() 메서드는 반복이 가능한지 검사하여 next() 메서드가 안전하게 호출될 수 있도록 보장한다. 만약 hasNext()가 없다면, 상태 검사 없이 next()만 호출하기 위해 아래와 같은 코드를 작성해야 할 것이다:
이러한 코드는 코드가 장황하고 복잡할 뿐만 아니라, 성능 문제를 유발하고 예외 남용으로 인해 버그 발생 가능성을 높인다. 정상적인 흐름에서 예외를 남용하는 것은 가독성을 떨어뜨리고 유지보수를 어렵게 만든다. 따라서 상태 검사 메서드를 제공하여 호출 전에 상태를 확인하는 것이 더 바람직하다.
2) 상태 검사 메서드와 대체 방안
상태 검사 메서드 외에도 옵셔널(Optional)이나 특정한 반환 값을 사용하는 방법도 있다.
이는 상태 의존적 메서드가 특정 조건에서 호출될 수 없을 때 예외를 던지는 대신 빈 옵셔널이나 특수한 값을 반환하여 호출자에게 해당 상황을 전달하는 방식이다. 이러한 접근 방식은 예외적인 상황을 줄여 호출자가 보다 명확하게 대처할 수 있게 해준다.
다음은 상태 검사 메서드 대신 사용할 수 있는 몇 가지 대안과 그에 대한 지침또한 그를 하나의 그림으로 아래와 같이 표현해봤다.
옵셔널(Optional) 또는 특수 값 반환
외부 동기화 없이 여러 스레드가 동시에 접근하거나 외부 요인에 의해 상태가 변할 수 있는 경우에는 상태 검사 메서드보다는 옵셔널이나 특정한 반환 값을 사용하는 것이 좋다. 상태 검사 메서드와 상태 의존적 메서드 호출 사이에 객체의 상태가 변할 위험이 있기 때문이다.
성능 문제 고려
성능이 중요한 경우 상태 검사 메서드가 상태 의존적 메서드와 작업 일부를 중복 수행하게 된다면 옵셔널이나 특수 값을 반환하는 것이 좋다. 두 메서드를 호출하면 중복 작업으로 인한 성능 저하가 발생할 수 있기 때문이다.
일반적인 경우 상태 검사 메서드 사용
성능이 특별히 중요한 상황이 아니고 외부 동기화 문제도 없다면 상태 검사 메서드를 사용하는 것이 일반적으로 더 좋은 선택이다. 상태 검사 메서드를 사용하면 코드의 가독성이 높아지고, 상태 검사 없이 상태 의존적 메서드를 잘못 호출하면 즉시 예외가 발생해 버그를 쉽게 발견할 수 있기 때문이다.
3. 핵심 정리
정상적인 흐름에서는 예외를 사용하지 않기: 예외는 예외적인 상황에서만 사용되어야 하며, 일반적인 흐름에서 예외를 남용하는 것은 지양해야 한다.
상태 검사 메서드 제공: 상태 의존적 메서드가 존재한다면 이를 안전하게 호출할 수 있는지 확인할 수 있는 상태 검사 메서드를 제공해야 한다. 이를 통해 API 사용성을 높이고 코드의 가독성을 유지할 수 있다.
옵셔널 및 특수 값 사용: 외부 요인으로 인해 객체의 상태가 변할 가능성이 있거나 성능 이슈가 있을 경우, 상태 검사 메서드 대신 옵셔널이나 특수 값을 반환하는 방식을 고려할 수 있다.
이러한 원칙을 준수하면 API 사용성이 높아지고 유지보수성이 크게 향상된다. 예외를 코드 흐름에서 남용하지 않는 것이 중요하며, 상태 검사 메서드를 통해 호출자가 메서드를 안전하게 사용할 수 있게 하는 것이 바람직하다. 이를 통해 설계가 견고해지고, 시스템 유지보수가 쉬워지며 예외적인 상황 처리가 명확해진다.
참고 및 출처
Last updated
Was this helpful?