CS/클린코드

[클린코드] chapter-7 오류처리

yhsim98 2022. 9. 1. 18:03
  • 오류 처리 코드로 인해 프로그램 논리를 이해하기 어려워진다면 깨끗한 코드라 부르기 어렵다

오류 코드보다 예외를 사용해라

  • 오류 코드를 반환하는 것이 아니라 예외를 사용해라
  • 실제 로직과 예외처리 부분이 나뉘어져 깔끔해진다

미확인 예외를 사용하라

  • checkedException을 사용하면 OCP 원칙에 어긋난다
  • 하위 모듈이 checked 예외를 던지면 상위 모든 모듈이 throw를 선언부에 추가해야 한다
  • 아주 중요한 라이브러리에서 예외를 꼭 처리해야 하는 경우를 제외하고는 uncheckedException을 사용해라

예외에 의미를 제공해라

  • 예외를 던질 때 전후 상황을 충분히 덧붙인다
  • 그러면 오류가 발생한 원인과 위치를 찾기 쉬워진다
  • 오류 메시지에 정보를 담아 예외와 함께 던져라

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

  • 오류 발생 컴포넌트로 분류 등 오류가 발생한 위치로 오류 분류가 가능하다
  • 또는 유형으로도 분류가 가능하다
    • 디바이스 실패, 네트워크 실패, 프로그래밍 오류 등등
  • 하지만 애플리케이션에서 오류를 정의할 때, 프로그래머에게 가장 중요한 관심사는 오류를 잡아내는 방법이 되어야 한다
// Bad
  // catch문의 내용이 거의 같다.

  ACMEPort 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 response exception");
  } finally {
    ...
  }
  // Good
  // ACME 클래스를 LocalPort 클래스로 래핑해 new ACMEPort().open() 메소드에서 던질 수 있는 exception들을 간략화

  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 PortDeviceFailure(e);
      }
    }
    ...
  }
  • 아래 코드는 호출하는 라이브러리 API을 감싸 던지는 예외를 잡아 던지는 wrapper 클래스를 사용하여 중복을 해결했다
  • 이같은 wrapper 클래스는 매우 유용하다
  • 외부 라이브러리와 프로그램 사이에서 의존성이 크게 줄어듬과 함께 나중에 다른 라이브러리로 갈아타도 비용이 적다
  • 또한 wrapper 클래스에서 외부 API를 호출하는 대신 테스트 코드를 넣어주는 방법으로 프로그램을 테스트하기도 쉬워진다
  • 마지막으로 특정 업체가 API를 설계한 방식에 발목 잡히지 않는다, 프로그램이 사용하기 편리한 API를 정의하면 그만이다

정상 흐름을 정의하라

  try {
    MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
    m_total += expenses.getTotal();
  } catch(MealExpensesNotFound e) {
    m_total += getMealPerDiem();
  }
  • 예외 상황을 통해 로직을 처리하고 있다
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
  • 그것보다는 청구했으면 청구한 값을, 아니면 기본값을 반환하는 방식으로 해라

    null을 반환하지 마라

  • null 을 반환하는 코드는 할 일을 늘린다

  • nullPointerException 발생하기도 쉽다

  • null 을 감싸서 반환하던가 예외를 날려라

    null 전달하지 마라

  • 전달하는 것은 더 나쁘다

  • null이 들어오면 처리해야 하는 일이 많아진다 넘기지 마라