Effective Java 2판 - 9장 정리 (Exception, 예외)

 

1. 개요

Exception은 활용하기에 따라 가독성, 안정성, 유지보수성을 향상시킬 수 있지만 잘못 활용하면 역효과도 내는 친구입니다. Exception을 잘 활용하기 위한 방법을 살펴봅시다.

2. Exception

규칙 57: 예외는 예외적 상황에만 사용하라.

1)     예외는 예외적 상황을 위해 고안된 것이기 때문에, JVM을 구현하는 사람 입장에서 보면 명시적 테스트만큼 빠르게 만들 이유가 별로 없다.

2)     try-catch 블록 안에 넣어둔 코드에는 최신 JVM이 사용하는 최적화 기법 가운데 일부가 적용되지 않는다.

즉 이름이 말하듯, 예외는 예외적인 상황에만 사용해야 한다. 평상시 제어 흐름에 이용해서는 안 된다. 쉽게 이해할 수 있는 표준적인 숙어대로 코딩해야지, 너무 머리를 많이 굴리면 곤란하다.

이는 API 설계에도 적용된다. 잘 설계된 API는 클라이언트에게 평상시 제어 흐름의 일부로 예외를 사용하도록 강요해서는 안 된다.

 

규칙 58: 복구 가능 상태에는 compiletime Exception(checked Exception)을 사용하고, 프로그래밍 오류에는 Runtime Exception(Unchecked Exception)을 사용해라.

caller 측에서 복구할 것으로 여겨지는 상황에 대해서는 compileTime Exception을 사용해야 한다.

단 프로그래밍 오류를 표현할 때는 runtime Exception이다.

 

규칙 59: 불필요한 compileTime Exception 사용은 피하라.

반란북에서 다룬 내용이다.

https://rawshrimpsushi.tistory.com/34

을 참조하자.

 

규칙 60: 표준 예외를 사용하라.

자바 플랫폼 라이브러리에는 대부분의 API가 필요로 하는 runtime Exception이 갖춰져 있다. 이를 재사용하면 배우기 쉽고 편리한 API를 만들 수 있다. 또한 가독성이 높다. 그리고 예외 클래스 개수를 줄여 메모리 요구량이 줄어들고, 클래스를 로딩하는 시간도 줄어든다.

주로 사용되는 Exception은 다음을 참조하라.

 

IllegalArgumentException: null이 아닌 인자의 값이 잘못되었을 때
IllegalStateException: 객체 상태가 메서드 호출을 처리하기에 적절치 않을 때
NullPointerException: null 값을 받으면 안 되는 인자에 null이 전달되었을 때
IndexOutOfBoundException: 인자로 주어진 첨자가 허용 범위를 벗어났을 때
ConcurrentModificationException: 병렬적 사용이 금지된 객체에 대한 병렬 접근이 탐지되었을 때
UnsupoortedOperationException: 객체가 해당 메서드를 지원하지 않을 때

 

한마디: 예외를 커스터마이즈 하는 것은 정말 논란이 많은 일인 것 같다. 회사에 문서화가 잘 되어 있으며 공유가 잘 된다면 나쁜 점만 있다고 생각치는 않지만 여러 고려 사항이 필요할 것 같다.

 

규칙 61: 추상화 수준에 맞는 예외를 던져라

메서드가 하는 일과 뚜렷한 관련성이 없는 예외가 메서드에서 발생하면 당혹스럽다. 추상화 수준이 낮은 곳에서 발생한 예외를 그대로 밖으로 던지면 이런 일이 생긴다.

따라서 상위 계층에서는 하위 계층에서 발생하는 예외를 반드시 받아서 상위 계층 추상화 수준에 맞는 예외로 바꿔서 던져야 한다.

 

규칙 62: 메서드에서 던져지는 모든 예외에 대해 문서를 남겨라.

compileTime Exception은 독립적으로 선언하고, 해당 예외가 발생하는 상황은 Javadoc @throws 태그를 사용해서 정확하게 밝혀라.

 

Javadoc @throws 태그를 사용해서 메서드에서 발생 가능한 모든 runtime Exception에 대한 문서를 남겨라. 하지만 메서드 선언부의 throws 뒤에 runtime Exception을 나열하진 마라.

 

같은 이유로 동일한 예외로 던지는 메서드가 많다면, 메서드마다 문서를 만드는 대신, 해당 예외에 대한 문서는 클래스의 문서화 주석에 남겨도 된다.

 

규칙 63: 어떤 오류인지를 드러내는 정보를 상세한 메시지에 담으라.

쉽게 재현할 수 없는 오류였다면 정보를 많이 얻어내는 것이 중요하다. 따라서 toString 메서드가 반환하는 문자열에 오류 원인에 관계된 정보를 최대한 많이 담는 것이 아주 중요하다. 오류 정보를 포착해 내기 위해서는, 오류의 상세 메시지에 예외에 관계된모든 인자와 필드의 값을 포함시켜야 한다.

 

규칙 64: 실패 원자성 달성을 위해 노력하라.

예외를 던지고 난 뒤에도 객체의 상태가 잘 정의된, 사용 가능한 상태로 남아 있으면 좋다. 호출자 측에서 오류 상태를 복구할 가능성이 있는 compileTime Exception이라면 특히 더 그렇다.

일반적으로 이야기해서, 메서드 호출이 정상적으로 처리되지 못한 객체의 상태는, 메서드 호출 전과 동일해야 한다. 이를 실패 원자성이라고 한다.

이를 달성하기 위해선 다음과 같은 방법이 있다.

 

1)     변경 불가능 객체로 설계한다.

2)     실패할 가능성이 있는 코드를 전부 객체 상태를 바꾸는 코드 앞에 배치한다.

3)     연산 수행 도중에 발생하는 오류를 가로채는 복구 코드를 작성한다.

4)     객체의 임시 복사본상에서 필요한 연산을 수행하고 연산이 끝난 다음에 임시 복사본 내용으로 객체를 바꾼다.

 

규칙 65: 예외를 무시하지 마라.

자명해보이지만 위반되는 경우가 많다. catch 블록은 예외를 선언한 목적, 그러니깐 예외적 상황을 반드시 처리하도록 강제한다는 목적에서 배치된다. 적어도 catch 블록 안에는 예외를 무시해도 괜찮은 이유라도 주석으로 남겨야 한다.