Chapter 7 - Error Handling

오류처리를 프로그램 논리와 분리하면 독립적인 추론이 가능해지며 유지보수성도 높아진다

  • 여기저기 흩어진 오류 처리 코드 때문에 실제 코드가 하는 일을 파악하기가 어려워질 때가 많음

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


  • if-else문 남발대신 try-catch문으로 깔끔하게 처리

    public void sendShutDown(){
      try {
        tryToShutDown();
      } catch (DeviceShutDownError e) {
        logger.log(e)
      }
    }
    
    private void tryToShutDown() throws DeviceShutDownError{
      ....
    }
    
  • 각 개념을 독립적으로 분리해서 사용

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


  • TDD를 사용해 필요한 나머지 논리를 추가

미확인 예외를 사용하라


  • C#, C++, Python, Ruby 등은 확인된 예외 지원 X

  • 확인된 오류는 OCL (Open Closed Principle) 위반
    • 확인된 예외가 던져지지만 catch 블록이 세 단계 위에 있다면 그 사이 메서드에서 선언부에 해당 예뢰를 정의해야 함
    • \(\Rightarrow\) 하위단계에서 코드를 변경하면 상위 단계 메서드 선언부를 전부 고쳐야 함!
  • 함수 내 다른 함수 호출이 많음. 즉, 하위 함수에서 확인된 오류를 던진다면 최상위 단계까지 연쇄적인 수정이 일어남 \(\Rightarrow\) Encapsulation 깨짐

  • 확인된 예외도 유용하지만, 일반적인 개발에서는 의존성이라는 비용이 이익보다 크다

예외에 의미를 제공하라


  • 예외를 던질 때 전후 상황을 충분히 덧붙이기 \(\rightarrow\) 오류 발생 원인과 위치 찾기가 쉬워짐
  • 오류 메시지에 정보를 담아 예외와 함께 던지기 (실패한 연산 이름, 유형, 로깅 기능 사용 등)

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


  • 오류 분류법 무궁무진
    • 오류 발생 위치로 분류 가능 (ex: 발생한 컴포넌트 등)
    • 오류 유형 (ex: 디바이스 실패, 네트워크 실패, 프로그래밍 오류 ...etc)

주 관심사: 오류를 잡아내는 방법

정상 흐름을 정의하라


  • 예제: 비용 청구 시 총계에 추가, 청구 안했다면 일일 기본 식비 추가

    try {
      MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
      m_total += expenses.getToal();
    } catch(MealExpesnseNotFound e) {
      m_total += getMealPerDiem();
    }
    
  • 더욱 간결하게:
    MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
    m_total += expenses.getTotal() 
    
    • 위 코드가 가능한 방법: expenseReportDAO를 고쳐 언제나 MealExpense객체를 반환하게 함 (청구한 식비가 없다면 일일 기본 식비를 반환하게)
      public class PerDiemMealExpenses implements MealExpenses {
        public int getTotal() { 
          // 기본값으로 일일 기본 식비 반환
        }
      }
      
  • \(\Rightarrow\) 특수 사례 패턴 (SPECIAL CASE PATTERN)
    • 클래스를 만들거나 객체를 조작해 특수 사례 처리하는 방식
    • \(\Rightarrow\) 클라 코드에서 예외처리 필요 X (클래스/객체에서 encapsulation해줌)

null을 반환하지 마라


public void registerItem(Item item) {
  if (item != null) {
    ItemRegistry registry = persistentStore.getItemRegistry();
    if (registry != null) {
      Item existing = registry.getItem(item.getId());
      if (existing.getBillingPeriod().hasRetailOnwer()) {
        existing.register(item);
      }
    }
  }
}
  • null인지 확인하는 코드가 너무 많다. null 대신 빈 리스트나 객체를 반환한다면 더 나아질 것 (깔-끔)
  • (참고) 2행에 persistentStore null check가 빠져있다.

null을 전달하지 마라


  • null반환도 나쁘지만 **null 전달은 더 나쁘다 **
  • 대다수 프로그래밍 언어는 호출자가 실수로 넘기는 null을 적절히 처리 못함
  • 애초에 null을 넘기지 못하도록 금지하는 정책이 나음
  • 즉, null이 인수로 넘어오는 코드면 문제 많음!!