관리 메뉴

너와 나의 스토리

[리팩터링] 오류 코드를 예외로 바꾸기 Replace Error Code with Exception 본문

개발/Refactoring

[리팩터링] 오류 코드를 예외로 바꾸기 Replace Error Code with Exception

노는게제일좋아! 2022. 2. 9. 08:22
반응형

배경

  • 오류가 발견되면 예외를 던진다. 그러면 적절한 예외 핸들러를 찾을 때까지 콜스택을 타고 위로 전파된다.
  • 예외를 사용하면 오류 코드를 일일이 검사하거나 오류를 식별해 콜스택 위로 던지는 일을 신경 쓰지 않아도 된다.
  • 예외는 프로그램의 정상 동작 범주에 들지 않는 오류를 나타낼 때만 쓰여야 한다. 
    • 예외를 던지는 코드를 프로그램 종료 코드로 바꿔도 프로그램이 여전히 정상 동작할지를 따져보고, 정상 동작하지 않을 것 같다면 예외를 사용하지 말라는 신호이다.
    • 이런 경우, 예외 대신 오류를 검출하여 프로그램을 정상 흐름으로 되돌리게끔 처리해야 한다.

 

절차

  1. 콜스택 상위에 해당 예외를 처리할 예외 핸들러를 작성한다.
  2. 테스트한다.
  3. 해당 오류 코드를 대체할 예외와 그 밖의 예외를 구분할 식별 방법을 찾는다.
  4. 정적 검사를 수행한다.
  5. catch 절을 수정하여 직접 처리할 수 있는 예외는 적절히 대처하고 그렇지 않은 예외는 다시 던진다.
  6. 테스트한다.
  7. 오류 코드를 반환하는 곳 모두에서 예외를 던지도록 수정한다. 하나씩 수정할 때마다 테스트한다.
  8. 모두 수정했다면 그 오류 코드를 콜스택 위로 전달하는 코드를 모두 제거한다. 하나씩 수정할 때마다 테스트한다.

 

 

예시

  • 전역 테이블에서 배송지의 배송 규칙을 알아내는 코드를 생각해보자.
    •  
    • function localShippingRules(country) { const data = countryData.shippingRules[country]; if (data) return new ShippingRules(data); else return -23; }
  • 이 코드는 country가 유효한지를 이 함수 호출 전에 다 검증했다고 가정한다.
  • 이 함수에서 오류가 난다면 다음과 같이 호출한 곳에서 반환된 오류 코드를 검사하여 오류가 발견되면 위로 전파한다. 
  • function calculateShippingCosts(anOrder){
        const shippingRules = localShippingRules(anOrder.coutnry);
        if (shippingRules < 0) return shippingRules; // 오류 전파
    }
  • 더 윗단 함수는 오류를 낸 주문을 오류 목록에 넣는다.
  • const status = calculateShippingCosts(orderData);
    if (status < 0) errorList.push({ order: orderData, errorCode: status });
  • 여기서 고려할 것들
    • localShippingRules()는 배송 규칙들이 countryData에 제대로 반영되어 있다고 가정해도 되나?
    • country 인수가 전역 데이터에 저장된 키드리과 일치하는 곳에서 가져온 것인가? 아니면 앞서 검증을 받았나?
  • 이 질문들의 답이 긍정적이면(예상할 수 있는 정상 동작 범주에 든다면) 오류 코드를 예외로 바꾸는 이번 리팩터링을 적용할 준비가 된 것이다.
  • [Step 1] 최상위에 예외 핸들러를 작성한다. 
    • calculateShippingCosts() 호출을 try 블록으로 감싼다.
    • try {
          const status = calculateShippingCosts(orderData);
      } catch (e) {
          // 예외 처리 로직
      }
      if (status < 0) errorList.push({ order: orderData, errorCode: status });
    • 그렇다고 이렇게 하면 status 선언이 try 블록으로 국한되기 때문에 조건문에서 검사할 수 없다. 
      • 그래서 status 선언과 초기화를 분리해야 한다.
    • let status;
      try {
          status = calculateShippingCosts(orderData);
      } catch (e) {
          throw e;
      }
      if (status < 0) errorList.push({ order: orderData, errorCode: status });
      • 잡은 예외는 모두 다시 던져야 한다. 
      • 호출하는 쪽 코드의 다른 부분에서도 주문을 오류 목록에 추가할 일이 있을 수 있으니 적절한 핸들러가 이미 구비되어 있을 수 있다. 
    • [Step 3] 해당 오류 코드를 대체할 예외와 그 밖의 예외를 구분할 식별 방법을 찾는다.
      • 이번 리팩터링으로 추가된 예외만을 처리하고자 한다면 다른 예외와 구별할 방법이 필요하다.
      • 예외를 클래스 기반으로 처리하는 프로그래밍 언어가 많은데, 이런 경우라면 서브클래스를 만드는 게 가장 자연스럽다. 
        • 현재의 자바스크립트는 여기에 해당하지 않지만 이렇게 하는게 좋다고 한다.
      • class OrderProcessingError extends Error {
            constructor(errorCode) {
                super(`주문 처리 오류: ${errorCode}`);
                this.code = errorCode;
            }
            get name() { return "OrderProcessingError" };
        }
    • [Step 5] catch 절을 수정하여 직접 처리할 수 있는 예외는 적절히 대처하고 그렇지 않은 예외는 다시 던진다.
      • let status;
        try {
            status = calculateShippingCosts(orderData);
        } catch (e) {
            if (e instanceof OrderProcessingError)
                errorList.push({ order: orderData, errorCode: status });
            else
                throw e;
        }
        if (status < 0) errorList.push({ order: orderData, errorCode: status });
    • [Step 7] 오류 검출 코드를 수정하여 오류 코드 대신 이 예외를 던지도록 한다.
      • function localShippingRules(country) {
            const data = countryData.shippingRules[country];
            if (data) return new ShippingRules(data);
            else throw new OrderProcessingError(-23);  // Before: return -23
        }
    • [Step 8] 모두 수정했다면 그 오류 코드를 콜스택 위로 전달하는 코드를 모두 제거한다.
      • 제거하기 전에 다음처럼 함정을 추가한 후 테스트해보면 좋을 것 같다.
      • function calculateShippingCosts(anOrder) {
            const shippingRules = localShippingRules(anOrder.coutnry);
        
            // Before: if (shippingRules < 0) return shippingRules; // 오류 전파
            if (shippingRules < 0) throw new Error("오류 코드가 다 사라지지 않았습니다.");
        }
      • 이 함정(new Error)에 걸리지 않는다면, 이 줄 전체를 제거해도 안전하다. 
      • function calculateShippingCosts(anOrder) {
            const shippingRules = localShippingRules(anOrder.coutnry);
            // 다른 코드들
        }
      • // 최상위 함수
        try {
            calculateShippingCosts(orderData);
        } catch (e) {
            if (e instanceof OrderProcessingError)
                errorList.push({ order: orderData, errorCode: e.code });
            else
                throw e;
        }

 

 

출처:

- [리팩터링 2판]

반응형
Comments