본문 바로가기

도서/Clean Code

[Clean Code/클린 코드] 7장 오류 처리

7장 오류 처리

📖  null을 전달하지 마라

 

 

목차

     


    🗒️ 책에서 기억하고 싶은 내용

    Try-Catch-Finally문부터 작성하라

    먼저 강제로 예외를 일으키는 테스트 케이스를 작성한 후 테스트를 통과하게 코드를 작성하는 방법을 권장한다.
    그러면 자연스럽게 try 블록의 트랜잭션 범위부터 구현하게 되므로 범위 내에서 트랜잭션 본질을 유지하기 쉬워진다.

    미확인(unchecked) 예외를 사용하라

    대규모 시스템에서 호출이 일어나는 방식을 상상해보라.
    최상위 함수가 아래 함수를 호출한다. 아래 함수는 그 아래 함수를 호출한다.
    단계를 내려갈수록 호출하는 함수 수는 늘어난다.

    이제 최하위 함수를 변경해 새로운 오류를 던진다고 가정하자.
    확인된 오류를 던진다면 함수는 선언부에 throws절을 추가해야 한다.
    그러면 변경한 함수를 호출하는 함수 모두가
    1) catch 블록에서 새로운 예외를 처리하거나
    2) 선언부에 throw절을 추가해야 한다는 말이다.

    결과적으로 최하위 단계에서 최상위 단계까지 연쇄적인 수정이 일어난다!
    throw경로에 위치하는 모든 함수가 최하위 함수에서 던지는 예외를 알아야 하므로 캡슐화가 깨진다.

    호출자를 고려해 예외 클래스를 정의하라

    다음은 외부 라이브러리를 호출하는 try-catch-finally문을 포함한 코드로,
    외부 라이브러리가 던질 예외를 모두 잡아낸다.

    ACMEprot port = new ACMEport(12);
    
    try {
      port.open();
    } catch (DeviceResponseException e) {
      reportPortError(e);
      logger.log("Device response exception", e);
    } catch (ATM1212UnlockedException e) {
      reportPortError(e);
      logger.log("Unlock exception", e);
    } catch (GMXError e) {
      reportPortError(e);
      logger.log("Device reponse exception");
    } finally {
      ...
    }

    대다수 상황에서 우리가 오류를 처리하는 방식은 비교적 일정하다.
    1) 오류를 기록한다.
    2) 프로그램을 계속 수행해도 좋은지 확인한다.

    위 경우는 호출하는 라이브러리 API를 감싸면서 예외 유형 하나를 반환하면 된다.

    LocalPort port = new LocalPort(12);
    try {
      port.open();
    } catch (PortDeviceFailure e) {
      reportError(e);
      logger.log(e.getMessage(), e);
    } finally {
      ...
    }
    
    public class LocalPort {
      private ACMEPort innerPort;
    
      public LocalPort(int portNumber) {
        innerPort = new ACMEPort(portNumber);
      }
    
      public void open() {
        try {
          innerPort.open();
        } catch (DeviceResponseException e) {
          throw new PortDeviceFailure(e);
        } catch (ATM1212UnlockedException e) {
          throw new PortDeviceFailure(e);
        } catch (GMXError e) {
          throw new PortDeivceFailure(e);
        }
      }
    }

    여기서 LocalPort 클래스는 단순히 ACMPort 클래스가 던지는 예외를 잡아 변환하는 감싸기(wrapper) 클래스일 뿐이다.

    외부 API를 감싸면
    1) 외부 라이브러리와 프로그램 사이에서 의존성이 크게 줄어든다. 나중에 다른 라이브러리로 갈아타도 비용이 적다.
    2) 외부 API를 호출하는 대신 테스트 코드를 넣어주는 방법으로 프로그램을 테스트하기도 쉬워진다.
    3) 특정 업체가 API를 설계한 방식에 발목 잡히지 않는다.

    null을 반환하지 마라

    리스트를 반환하는 메서드가 값이 없을 때 null 대신 빈 리스트를 반환하도록 수정한다.
    Collections.emptyList()

     

    🗒️ 소감

    '호출자를 고려해 예외 클래스를 정의하라' 부분에서,
    수정 전후 코드를 비교하며 wrapper 클래스의 장점을 알려주어 쉽게 이해할 수 있었다.
    코드도 함께 두고 보고 싶어서 위에 가지고 왔다.

    오류 처리와 테스트 코드를 작성해 본 경험이 적기 때문에 전체적으로 와닿지 않아 여러 번 읽었다.
    이후에도 여러 번 다시 읽어보고, 더 경험이 쌓였을 때 유익하게 사용해 보고 싶은 챕터였다.

     

    🗒️ 궁금하거나 어려웠던 내용

    확인된 예외(checked exception) : 컴파일 단계에서 발생하는 예외
    미확인 예외(unchecked exception : 프로그램 실행 중 발생하는 예외