글 검색

공개된 글의 제목과 본문을 검색합니다.

    닫기 Esc

    69. 예외는 진짜 예외 상황에만 사용하라#

    잘못된 예외처리#

    java
    try {
        int i = 0;
        while (true)
            range[i++].climb()
    } catch (ArrayIndexOutOfBoundsException e) {
    }
    배열의 경계를 검사하는 예외코드 (예외 블랙홀)

    해당 코드가 잘못된 3가지 이유

    1. 에외는 예외 상황에 쓸 용도로 설계되었으므로 JVM 구현자가 최적화를 하지 않았을 수 있다.
    2. try-catch 블록 안에 넣으면 JVM이 적용할 수 있는 최적화가 제한된다.
    3. 배열을 순회하는 관용구는 앞서 걱정한 중복 검사를 수행하지 않게 최적화한다.

    추가적으로 버그가 숨어있다면 디버깅하기 어려웠을 것이다.

    예외를 잘 쓰는 방법.#

    • 예외는 예외 상황에서만 써라. 일상적인 제어 흐름용으로 쓰지 마라.
    • 잘 설계된 API라면 클라이언트가 정상적인 제어 흐름에서 예외를 사용할 일이 없게 해야한다.
    • 특정 상태에서만 호출할 수 있는 상태 의존적 메서드를 제공하는 클래스는 상태 검사 메서드도 함께 제공해야 한다.
      • Iterator인터페이스의 next와 hasNext가 각각 상태 의존적 메서드와 상태 검사 메서드에 해당

    상태 검사 메서드 대신 사용할 수 있는 선택지#

    상태 검사 메서드 대신 빈 Optional<>이나 null을 반환할 수도 있다.

    1. 외부 동기화 없이 여러 스레드가 접근하거나 외부 요인으로 상태가 바뀔 수 있다면 널이나 특정 값을 사용한다.
    2. 성능이 중요한데 상태 검사 메서드가 상태 의존적 메서드의 작업 일부를 중복 수행한다면 옵셔널이나 특정 값을 선택한다.
    3. 다른 경우 상태 검사 메서드 방식이 조금 더 낫다.
      1. 가독성이 살짝 더 좋고, 잘못 사용했을 때 발견하기가 쉽다.

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

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


    77. 예외를 무시하지 말라#

    전체 글 둘러보기