프로그래밍

에러 핸들링 잘하기 (feat. 클린코드)

클린 코드 - 에러 핸들링

오류 코드보다는 예외 사용하기

오류 코드를 사용하게 되면 상단에 오류인지 확인하는 불필요한 로직이 들어가게 됩니다. 오류의 범주에 들어가지 않은 상태를 나타내는 것이 아니라면, 예외(Exception)로 명시적으로 에러 처리를 표현해주는 게 좋습니다.

  • as-is

    from enum import Enum 
    
    class ErrorCodes(Enum):
        VALUE_ERROR="VALUE_ERROR"
    
    def we_can_raise_error():
        ...
        return ERROR_CODES.VALUE_ERROR
    
    def use_ugly_function():
        result = we_can_occur_error()
        if result == ErrorCodes.VALUE_ERROR:
            # 처리 코드
        ...
  • to-be

    def we_can_raise_error():
        if ...
            raise ValueError("에러 발생")
    
    def use_awesome_function():
        try:
            we_can_occur_error()
            ...
        except ValueError as e:
            # 에러 처리 로직                
    

예외 클래스 잘 정의하기

에러 핸들링 잘하기

  • 에러를 포착했다면 잘 핸들링해줘야 합니다.

    def we_can_raise_error():
        ...
        raise Exception("Error!")
    
    # BAD: 에러가 났는지 확인할 수 없게 됩니다.
    def use_ugly_function1():
        try:
            we_can_raise_error()
            ...
        except:
            pass
    
    # BAD: 로그만 남긴다고 끝이 아닙니다.
    def use_ugly_function2():
        try:
            we_can_raise_error()
            ...
        except Exception as e:
            print(f"에러 발생{e}")
    
    # GOOD
    def use_awesome_function():
        try:
            we_can_raise_error()
            ...
        except Exception as e:
            logging.error(...) # Error Log 남기기
            notify_error(...) # 예측 불가능한 외부 I/O 이슈라면 회사 내 채널에 알리기(이메일, 슬랙 etc)
            raise OtherException(e) # 만약 이 함수를 호출하는 다른 함수에서 추가로 처리해야 한다면 에러를 전파하기
        finally:
            ... #에러가 발생하더라도 항상 실행되어야 하는 로직이 있다면 finally 문을 넣어주기
  • 에러 핸들링을 모을 수 있으면 한곳으로 모읍니다. 보통 같은 수준의 로직을 처리한다면 한 곳으로 모아서 처리하는 게 더 에러를 포착하기 쉽습니다.

    • as-is

      def act_1():
          try:
              we_can_raise_error1()
              ...
          except:
              #handling
      
      def act_2():
          try:
              we_can_raise_error2()
              ...
          except:
              #handling
      
      def act_3():
          try:
              we_can_raise_error3()
              ...
          except:
              #handling
      
      # 에러가 날 지점을 한눈에 확인할 수 없습니다. 
      # act_1이 실패하면 act_2가 실행되면 안 된다면? 핸들링하기 어려워집니다.
      def main():
          act_1()
          act_2()
          act_3()
      
    • to-be

      def act_1():
          we_can_raise_error1()
          ...
      
      def act_2():
          we_can_raise_error2()
          ...
      
      def act_3():
          we_can_raise_error3()
          ...
      
      # 직관적이며 에러가 날 지점을 확인하고 처리할 수 있습니다.
      # 트랜잭션같이 한 단위로 묶여야하는 처리에도 유용합니다.
      def main():
          try:
              act_1()
              act_2()
              act_3()
          except SomeException1 as e1:
              ...
          except SomeException2 as e2:    
              ...
          except SomeException2 as e3
              ...
          finally:
              ...