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