69. 예외는 진짜 예외 상황에만 사용하라
잘못된 예외처리
try {
int i = 0;
while (true)
range[i++].climb()
} catch (ArrayIndexOutOfBoundsException e) {
}
Java
복사
배열의 경계를 검사하는 예외코드 (예외 블랙홀)
해당 코드가 잘못된 3가지 이유
1.
에외는 예외 상황에 쓸 용도로 설계되었으므로 JVM 구현자가 최적화를 하지 않았을 수 있다.
2.
try-catch 블록 안에 넣으면 JVM이 적용할 수 있는 최적화가 제한된다.
3.
배열을 순회하는 관용구는 앞서 걱정한 중복 검사를 수행하지 않게 최적화한다.
추가적으로 버그가 숨어있다면 디버깅하기 어려웠을 것이다.
예외를 잘 쓰는 방법.
•
예외는 예외 상황에서만 써라. 일상적인 제어 흐름용으로 쓰지 마라.
•
잘 설계된 API라면 클라이언트가 정상적인 제어 흐름에서 예외를 사용할 일이 없게 해야한다.
•
특정 상태에서만 호출할 수 있는 상태 의존적 메서드를 제공하는 클래스는 상태 검사 메서드도 함께 제공해야 한다.
◦
Iterator인터페이스의 next와 hasNext가 각각 상태 의존적 메서드와 상태 검사 메서드에 해당
상태 검사 메서드 대신 사용할 수 있는 선택지
상태 검사 메서드 대신 빈 Optional<>이나 null을 반환할 수도 있다.
1.
외부 동기화 없이 여러 스레드가 접근하거나 외부 요인으로 상태가 바뀔 수 있다면 널이나 특정 값을 사용한다.
2.
성능이 중요한데 상태 검사 메서드가 상태 의존적 메서드의 작업 일부를 중복 수행한다면 옵셔널이나 특정 값을 선택한다.
3.
다른 경우 상태 검사 메서드 방식이 조금 더 낫다.
a.
가독성이 살짝 더 좋고, 잘못 사용했을 때 발견하기가 쉽다.
70. 복구할 수 있는 상황에는 검사 예외를, 프로그래밍 오류에는 런타임 예외를 사용하라
•
자바는 검사 예외, 런타임 예외, 에러로 구분되는 3가지 throwable을 지원하는데 언제/무엇을 사용해야 하는지 헷갈려 하는 프로그래머들이 종종있다
검사 예외 (Checked Exception)
•
호출하는 쪽에서 복구하리라 여겨지는 상황이라면 검사 예외를 사용하라.
•
검사 예외는 catch가 강제된다.
◦
따라서 검사 예외는 메서드를 호출했을 때 발생할 수 있는 유력한 결과임을 클라이언트에게 알려주는 것이다.
•
복구가 가능하다고 믿는 다면 검사 예외를 사용하자.
런타임 예외 (Runtime Exception)
•
프로그래밍 오류를 나타낼 때는 런타임 예외를 사용하자.
•
런타임 예외의 대부분은 전제조건을 만족하지 못했을 때 발생한다.
◦
클라이언트가 해당 API의 명세에 기록된 제약을 지키지 못했다는 뜻이다.
•
복구가 불가능하다고 생각된다면 런타임 예외를 사용하자.
에러 (Error)
•
에러는 보통 JVM이 자원 부족, 불변식 깨짐 등 더 이상 수행할 수 없는 상황을 나타낼때 사용된다.
•
Error는 상속하지도 말고, throw문도 던지는 일도 없어야 한다.
◦
AssertionError는 예외다.
71. 필요 없는 검사 예외 사용은 피하라
•
검사 예외를 싫어하는 자바 프로그래머가 많지만 제대로 활용하면 API와 프로그램의 질을 높일 수 있다.
•
검사 예외는 발생한 문제를 프로그래머가 처리하여 안전성을 높여준다.
•
물론 과하게 쓰면 오히려 쓰기 불편하게 된다.
•
어떤 메서드가 검사 예외를 던질 수 있게 선언됐다면, 이를 호출하는 쪽에서 catch 블록을 두거나 바깥으로 던져서 전파해야 한다.
◦
이는 클라이언트에게 부담을 준다.
◦
검사 예외를 던지는 메서드는 스트림안에서 직접 사용이 불가능하다.
검사 예외 회피방법
•
적절한 결과 타입을 담은 옵셔널을 반환한다.
•
검사 예외를 던지는 메서드를 2개로 쪼개 비검사 예외로 바꿀 수 있다.
불필요한 예외처리
try {
obj.action(args);
} catch (TheCheckedException e) {
... // 예외 상황에 대처한다.
}
Java
복사
리펙터링
if (obj.actionPermitted(args)) {
obj.action(args);
} else {
...
}
Java
복사
•
이 리팩터링을 모든 상황에 적용할 수도 없다.
◦
적용할 수만 있다면 더 쓰기 편한 API를 제공할 수 있다.
72. 표준 예외를 사용하라
•
숙련된 프로그래머는 더 많은 코드를 재사용한다.
•
예외도 마찬가지로 재사용하는것이 좋으며, 자바 라이브러리는 대부분 API에서 쓰기에 충분한 수의 예외를 제공한다.
표준 예외를 사용하면 좋은점
•
우리의 API가 다른 사람에게 쉽게 다가온다는 점이다.
◦
많은 프로그래머에게 익숙해진 규약을 그대로 따르기 때문이다.
•
우리의 API도 낯선 예외를 사용하지 않게 되어 읽기 쉽게 된다는 장점도 크다.
•
예외 클래스 수가 적을수록 메모리 사용량도 줄고 클래스를 적재하는 시간도 적게 걸린다.
주의점
•
Exception, RuntimeException, Throwable, Error는 직접 재사용하지 말자.
널리 사용되는 에러
예외 | 주요 쓰임 |
IllegalArgumentException | 호출자가 인수로 부적절한 값을 넘길 때 |
IllegalStateException | 대상 객체의 상태가 호출된 메서드를 수행하기에 적절하지 않을 때 |
NullPointerException | null 값을 허용하지 않는 메서드에 null을 건낼 때 |
IndexOutOfBoundsException | 시퀀스의 허용 범위 넘을 때 |
ConcurrentModificationException | 단일 스레드에서 사용하려고 설계한 객체를 여러 스레드가 동시에 수정하려고 할 때 |
UnsupportedOperationException | 클라이언트가 요청한 동작을 대상 객체가 지원하지 않을 때 |
•
인수 값이 무엇이든 실패했을 거라면 IllegalStateException, 그렇지 않으면 IllegalArgumentException을 던지자.
73. 추상화 수준에 맞는 예외를 던지라
•
수행하려는 일과 관련 없이 보이는 예외가 튀어나오면 당황스러울 것이다.
•
메서드가 저수준 예외를 처리하지 않고 바깥으로 전파해버릴 때 종종 일어나는 일이다.
•
당황시키는데 그치지 않고, 내부 구현 방식을 드러내어 윗 레벨 API를 오염시킨다.
해결 방법
•
이 문제를 피하려면 상위 계층에서는 저수준 예외를 잡아 자신의 추상화 수준에 맞는 예외로 바꿔 던져야한다.
◦
이를 예외 번역이라 한다.
try {
... // 저수준 추상화를 이용한다.
} catch (LowerLevelException cause) {
throw new HighetLevelException(cause);
}
Java
복사
예외 연쇄
•
예외를 번역할 때, 저수준 예외가 디버깅에 도움이 된다면 예외 연쇄를 사용하는게 좋다.
•
예외 연쇄란 근본 원인이 되는 저수준 예외를 고수준 예외에 실어 보내는 방식
◦
Throwable의 getCause메서드
•
대부분의 표준 예외는 예외 연쇄용 생성자를 갖추고 있다.
•
그렇지 않은 예외라도 Throwable의 initCause 메서드를 이용해 원인을 직접 못받을 수 있다.
•
무작정 예외를 전파하는 것보다 예외 번역이 우수한 방법이지만, 그렇다고 남용해서는 곤란하다.
◦
가능하면 저수준 메서드가 반드시 성공하도록 하여 아래 계층에서 예외가 발생하지 않도록 하는 것이 최선이다.
74. 메서드가 던지는 모든 예외를 문서화하라
•
메서드가 던지는 예외는 그 메서드를 올바로 사용하는데 아주 중요한 정보다.
•
각 메서드가 던지는 예외를 문서화 하는데 충분한 시간을 쏟아야 한다.
•
검사 예외는 항상 따로따로 선언하고, 각 예외가 발생하는 상황을 자바독의 @throws 태그를 사용하여 정확히 문서화하자.
◦
공통 상위 클래스 하나로 뭉뚱그려 선언하는 일은 삼가자
◦
유일한 예외는 main메서드로 main은 오직 JVM만이 호출하므로 Exception을 던지도록 선언해도 괜찮다.
•
비검사 예외도 정성껏 문서화해두면 좋다.
◦
비검사 예외는 프로그래밍 오류를 뜻하므로, 무엇인지 알려주면 프로그래머는 해당 오류가 나지 않도록 코딩하게 된다.
◦
잘 정비된 비검사 예외 문서는 그 메서드를 성공적으로 수행하기 위한 전제조건이 된다.
◦
발생 가능한 비검사 예외를 문서로 남기는 일은 인터페이스 메서드에서 특히 중요하다.
•
메서드가 던질 수 있는 예외를 각각 @throws 태그로 문서화하되, 비검사 예외는 메서드 선언의 throws 목록에 넣지 말자.
◦
자바독 유틸리티는 throws와 @throws 태그로 동시에 명시된 예외와, @throws 태그로만 명시한 예외를 구분해준다.
•
한 클래스에 정의된 많은 메서드가 같은 이유로 같은 예외를 던진다면 그 예외를 클래스 설명에 추가하는 방법도 있다.
◦
NullPointerException이 가장 흔한 사례다.
75. 예외의 상세 메시지에 실패 관련 전보를 담으라
•
예외를 잡지 못해 프로그램이 실패하면 자바 시스템은 그 예외의 Stack Trace정보를 자동으로 출력한다.
•
스택 추적은 예외 객체의 toString 메서드를 호출해 얻는 문자열로, 이 정보가 예외를 분석하기 위한 유일한 정보인 경우가 많다.
•
따라서 예외의 toString 메서드에 실패 원인에 관한 정보를 가능한 많이 담아 반환하는 일은 아주 중요하다.
•
실패 순간을 포착하려면 발생한 예외에 관여된 모든 매개변수와 필드의 값을 실패 메시지에 담아야 한다.
•
예외의 상세 메시지와 최종 사용자에게 보여줄 오류 메시지를 혼동해서는 안된다.
◦
최종 사용자에게는 친절한 메시지를 보여줘야 하는 반면, 예외 메시지는 가독성보다는 담긴 내용이 훨씬 중요하다.
•
포착한 실패 정보는 예외 상황 복구에 유용하다.
◦
따라서 비검사 예외보다 검사 예외에서 빛을 발한다.