////
Search

10장 - 예외

Created
2022/10/04 12:30
Tags

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 메서드에 실패 원인에 관한 정보를 가능한 많이 담아 반환하는 일은 아주 중요하다.
실패 순간을 포착하려면 발생한 예외에 관여된 모든 매개변수와 필드의 값을 실패 메시지에 담아야 한다.
예외의 상세 메시지와 최종 사용자에게 보여줄 오류 메시지를 혼동해서는 안된다.
최종 사용자에게는 친절한 메시지를 보여줘야 하는 반면, 예외 메시지는 가독성보다는 담긴 내용이 훨씬 중요하다.
포착한 실패 정보는 예외 상황 복구에 유용하다.
따라서 비검사 예외보다 검사 예외에서 빛을 발한다.

76. 가능한 한 실패 원자적으로 만들라.

77. 예외를 무시하지 말라