ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] Effective Java 3/E 정리 - 10장 예외
    Study/이펙티브 자바 2021. 7. 31. 20:29
    반응형

     

    예외를 제대로 활용하면 가독성, 신뢰성, 유지보수성을 높일 수 있다.

    이번 장에서 예외를 효과적으로 활용하는 방법을 알아보자.

     


     

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

    이번 장은 제목 그대로 예외는 오직 예외 상황에서만 사용하라는 것이다.

    일상적인 제어 흐름용으로 쓰인다면 가독성과 성능을 떨어뜨리는 뿐만 아니라

    제대로 동작하지 않거나, 버그를 숨겨 디버깅을 어렵게 할 수 있다.

     

    이 원칙은 API 설계에도 사용된다.

    특정 상태에서만 호출할 수 있는 '상태 의존적' 메서드를 제공하는 클래스는 '상태 검사'메서드도 제공해야 한다.

    예를 들면 Iterator 인터페이스의 next가 상태 의존적 메서드, hasNext가 상태 검사 메서드에 속한다.

    상태 검사 메서드 대신 빈 옵셔널 혹은 null과 같은 특수한 값을 반환하는 방법도 있다.

     

    아래 두가지 경우에는 옵셔널이나 특정 값을 반환하는 방법을 사용하는 것이 좋다.

    • 외부 동기화 없이 여러 스레드가 동시에 접근할 수 있거나 외부 요인으로 상태가 변할 수 있는 경우
      → 상태 검사 메서드와 의존적 메서드 호출 사이에 객체의 상태가 변할 수 있다.
    • 성능이 중요한 상황에서 상태 검사 메서드가 의존적 메서드의 작업 일부를 중복 수행할 경우

    그 외에는 상태 검사 메서드를 사용하는 것이 가독성이 더 좋고, 잘못 사용했을 때 발견하기 쉽다.

    그리고 상태 검사 메서드를 호출하는 것을 잊었다면 예외를 던져 버그를 확실히 잡을 수 있다.

    (옵셔널은 해당하지 않지만 특정값은 검사하지 않고 지나쳐 발견하기 어렵다)

     

     

    Item70. 복구할 수 있는 상황에는 검사 예외를,  프로그래밍 오류에는 런타임 예외를 사용하라

    자바는 문제 상황을 알리는 타입(throwable)으로 검사 예외,

    그리고 비검사 예외인 런타임 예외와 에러 이렇게 세가지를 제공한다.

     

    아래는 세가지 예외를 어느 상황에서 사용하면 좋은지에 대한 지침이다.

    • 호출하는 쪽에서 복구하리라 여겨지는 상황이라면 검사 예외를 사용하라.
    • 프로그래밍 오류를 나타낼 때는 런타임 예외를 사용하자.
      • 에러는 보통 JVM이 자원 부족, 불변식 깨짐 등 더 이상 수행을 할 수 없는 상황을 나타낼 때 사용한다.
        따라서 Error 클래스를 상속해 하위 클래스를 만드는 일은 자제해야 하고,
        비검사 예외는 모두 RuntimeException의 하위 클래스로 만들자.

    참고로 Exception, RuntimeException, Error를 상속하지 않는 throwable은 이로울게 없다. 사용하지 말자.

     

     

    Item71. 필요 없는 검사 예외 사용은 피하라

    검사 예외는 발생한 문제를 프로그래머가 처리하여 안전성을 높일 수 있다.

    그러나 과하게 사용한다면 API 사용자에게 부담을 준다.

    더구나 검사 예외를 던지는 메서드는 스트림안에서 사용할 수 없다.

    따라서 API를 제대로 사용해도 발생할 수 있는 예외거나,

    프로그래머가 조치를 취할 수 있는 경우가 아니라면 비검사 예외를 선택하는 것이 낫다.

    단 하나의 검사 예외라면 Item69에서 언급한 옵셔널이나 상태 검사 메서드를 활용하는 것도 좋다.

     

     

    Item72. 표준 예외를 사용하라

    표준 예외를 재사용하면 아래와 같은 장점이 있다.

    • API를 다른 사람이 익히고 사용하기 쉬워진다.
    • API를 사용한 프로그램도 가독성이 더 좋아진다.
    • 예외 클래스 수가 적을 수록 메모리 사용량도 줄고 클래스를 적재하는 시간도 줄어든다.

     

    자주 사용되는 예외  
    IllegalArgumentException 허용하지 않는 값이 인수로 건네졌을 때(null은 따로 NullPointerException으로 처리)
    IllegalStateException 객체가 메서드를 수행하기에 적절하지 않은 상태일 때
    NullPointerException null을 허용하지 않는 메서드에 null을 건넸을 때
    IndexOutOfBoundsException 인덱스가 범위를 넘어섰을 때
    ConcurrentModificationException 허용하지 않는 동시 수정이 발견됐을 때
    UnsupportedOperationException 호출한 메서드를 지원하지 않을 때

     

    Exception, RuntimeException, Throwable, Error는 직접 재사용하지 말자.

    이 클래스들은 다른 예외들의 상위 클래스이기때문에 안정적으로 테스트할 수 없다.

     

     

    Item73. 추상화 수준에 맞는 예외를 던져라

    종종 메서드가 저수준 예외를 처리하지 않고 바깥으로 전파하면

    프로그래머가 당황하는 것 뿐만 아니라 내부 구현 방식을 드러내어 윗 레벨 API를 오염시킨다.

    따라서 아래와 같이 상위 계층에서는 저수준의 예외를 잡아 자신의 추상화 수준에 맞는 예외로 던져야 한다. (예외 번역)

    try {
        ... // 저수준 추상화를 이용한다.
    } catch (LowerLevelException e) {
        // 추상화 수준에 맞게 번역한다.
        throw new HigherLevelException(...);
    }

     

    예외 연쇄란 문제의 근본 원인인 예외를 다른 예외에 실어보내는 방법으로,

    예외를 번역할 때 저수준 예외가 디버깅에 도움이 된다면 예외 연쇄를 사용한다.

    try {
        ... // 저수준 추상화를 이용한다.
    } catch (LowerLevelException cause) {
        // 저수준 예외를 고수준 예외에 실어 보낸다.
        throw new HigherLevelException(cause);
    }

     

    예외 연쇄는 문제의 원인을 프로그램에서 접근할 수 있게 해주고,

    원인과 고수준 예외의 스택 추적 정보를 잘 통합해준다.

    대부분 표준 예외는 예외 연쇄용 생성자를 갖추고 있다.

    그렇지 않은 예외라면 Throwable의 initCause 메서드를 이용해 원인을 지정할 수 있다.

     

    저수준 예외를 바로 전파하는 것보다는 예외 번역이 좋은 방법이지만, 아래 계층에서 처리하는 것이 가장 좋다.

     

     

    Item74. 메서드가 던지는 모든 예외를 문서화하라

    검사 예외는 항상 따로따로 선언하고,

    각 예외가 발생하는 상황을 자바독의 @throws 태그를 사용하여 문서화하자.

    특히 Exception이나 Throwable을 던진다는 것과 같이 뭉뚱그려 선언하면 안된다.

    예외가 있다면 main 메서드로, main은 오직 JVM만이 호출하므로 Exception을 던지도록 해도 된다.

     

    메서드가 던질 수 있는 예외를 각각 @throws 태그로 문서화하되,

    비검사 예외는 메서드 선언의 throws 목록에 넣지 말자.

    자바독 유틸리티는 메서드 선언의 throws 절에 등장하고 메서드 주석의 @throws 태그에도 명시한 예외와 

    @throws 태그에만 명시한 예외를 시각적으로 구분해주기 때문에 비검사 예외를 구분할 수 있다.

     

    한 클래스의 많은 메서드가 같은 이유로 같은 예외를 던진다면 메서드가 아닌 클래스 설명에 추가해도 된다.

    흔하게 NullPointerException은 클래스 문서화 주석에

    “이 클래스의 모든 메서드는 인수로 null이 넘어오면 NullPointerException을 던진다”라고 표기한다.

     

     

    Item75. 예외의 상세 메시지에 실패 관련 정보를 담으라

    예외를 잡지 못해 프로그램이 실패하면 자바 시스템은 그 예외의 스택 추적(stack trace) 정보를 자동으로 출력한다.

    스택 추적은 예외 객체의 toString 메서드를 호출해 얻는 문자열로, 이 정보가 예외를 분석하기 위한 유일한 정보인 경우가 많다.

    따라서 예외의 toString 메서드에 실패 원인에 관한 정보를 가능한 많이 담아 반환하는 것이 중요하다.

    발생한 예외와 관여된 매개변수와 필드의 값을 적절하게 실패 메시지에 담는 것이 좋다.

    (단, 보안과 관련된 정보는 피하도록 하자.)

     

     

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

    실패 원자성(failure-atomic)이란 호출된 메서드가 실패하더라도 해당 객체는 메서드 호출 전 상태를 유지하는 특성을 말한다.

    실패 원자성을 얻기위한 방법은 다음과 같다.

    • 불변 객체로 설계한다.
    • 작업 수행에 앞서 매개변수의 유효성을 검사한다.
    • 객체의 임시 복사본에서 작업을 수행한 다음, 작업이 성공하면 원래 객체와 교체한다.
    • 작업 도중 발생하는 실패를 사로채는 복구 코드를 작성하여 작업 전 상태로 되돌린다.

     

    Item77. 예외를 무시하지 말라

    API 설계자가 메서드 선언에 예외를 명시하는 이유는 적절한 조치가 필요하기 때문이다.

    아래와 같이 trye-catch문으로 감싼 후 아무 일도 하지 않으면 안된다.

    try {
    	...
    } catch (SomeException e) {
    }

     

    FileInputStream을 닫을 때와 같은 경우는 예외를 무시할 수 있지만,

    혹시 무시할 경우에는 catch 블록안에 그렇게 결정한 이유를 남기고 예외 변수 이름도 ignored로 바꿔놓자.

    Future<Integer> f = exec.submit(planarMap::chromaticNumber);
    int numColors = 4; // 기본값. 어떤 지도라도 이 값이면 충분하다
    try {
      numColors = f.get(1L, TimeUnit.SECONDS);
    } catch (TimeOutException | ExcutionException ignored) {
      // 기본값을 사용한다(색상 수를 최소화하면 좋지만, 필수는 아니다)
    }

     

    예외를 처리하지 않을 경우 프로그램을 오류를 내재한채 동작한다.

    글러다 어느 순간 문제의 원인과 아무 상관없는 곳에서 갑자기 죽을 수도 있다.

    오류를 당장 처리할 수 없다면 적어도 바깥으로 전파되게 하자.

    댓글