Clean Code

Clean Code "오류 처리"

오스타 2022. 4. 24. 22:32

Clean Code "오류 처리"

Clean Code 저서를 읽은 후 주관적인 내용을 많이 포함하고 있는 글이기 때문에 틀린 내용이나 생각이 다른 부분이 있다면 제안해주세요 :)

 

 

Javascript 예외 처리

Javscript에서는 다른 일부 언어와 같이 예외 처리를 위한 Error객체를 지원하고 있습니다.

Clean Code 저서에서는 길게 이어지는 분기 처리 형식의 오류 처리보다는 예외 처리 형식을 추천하고 있는데요.

Javascript에서는 예외 처리를 어떻게 처리하는지 간단하게 살펴보려 합니다.

 

function division(operand1: number, operand2: number): number {
  if (!operand2) {
    throw new Error('Invalid Operand')
    // throw 'Invalid Operand' - Error 객체 없이 문자열을 던질 시 우리는 예외 발생 stack을 살펴볼 수 없기 때문에 Error 객체를 활용하는 것이 좋음
  }
  return operand1 / operand2
}
​
try {
  console.log(division(3, 0))
} catch (e) {
  console.log(e)
}

Javascript에서도 try catch문을 활용해 예외(exception)들을 처리할 수 있습니다.

위 코드와 같이 특정 Error 객체를 throw하게 되면 catch문에서 해당 예외를 따로 처리해줄 수 있습니다.

Error 객체에 전달되는 첫 번 째 값을 우리는 향후 Error.message로 사용할 수 있습니다.

 

확인된 예외 처리 (특정 오류 처리)

Clean Code 저서서는 확인된 예외 처리에 관해 장단점을 가지고 있기 때문에 경우에 따라 특정 오류를 처리하거나 모두 묶어서 처리하는 코드를 구성할 것을 권유하고 있습니다.

예외를 모두 묶어서 처리하는 코드는 위에서 예시로 든 코드와 같습니다. catch문에서 획득한 Error객체를 분기처리 없이 동일하게 처리하는 모습입니다.

function example() {
  ...
}
​
try {
  example()
} catch (e) {
  if (e instanceof SyntaxError) {
    console.log(e)
  } else if (e instanceof RangeError) {
    console.log(e)
  } else if (e instanceof TypeError) {
    console.log(e)
  }
}

특정 종류의 예외만을 처리하는 코드입니다. 확인된 예외만을 처리하기 때문에 중요 라이브러리에서 모든 예외를 명시하고 잡아내야 하는 코드에서는 충분히 필요있는 코드일 것입니다. 하지만 그럴 필요가 없는 코드에서는 굳이 우리는 예외들을 분기처리할 필요가 없을 것입니다.

 

사용자 정의 에러 만들기

class MyError extends Error {
  constructor(message: string) {
    super(message)
    this.name = 'MyError'
  }
}

이렇듯 간단하게 기존 Error 객체를 확장시켜 커스텀 에러 객체를 만들 수 있습니다.

위 코드는 기본 정보만을 포함하고 있지만 필요한 정보를 포함하여 확장할 경우 우리는 더 깔끔하고 일관성 있는 오류 객체들을 구성해나갈 수 있을 것입니다.

 

 

Null을 반환하지도, 전달받지도 마라

Null을 전달하거나 전달받는 형태의 코드는 대응하기 어려운 코드이다.

null을 전달해줄도 있는 매개변수가 있다면 우리는 지속적으로 코드에 null check를 진행해주어야 할 것이다.

null 사용에 대한 유의사항은 Typescript에서 strictNullChecks옵션을 활용하면 쉽게 코드에서 null 체크를 진행할 수 있습니다.

interface Point {
  x: number
  y: number
}
​
function calculateDistance(spot1: Point, spot2: Point) {
  return Math.abs(spot2.x - spot1.x) + Math.abs(spot2.y - spot1.y)
}
​
console.log(calculateDistance(null, { x: 1, y: 2 })) // 'null'형식의 인수는 'Point'형식의 매개변수에 할당될 수 없습니다.

물론 실제 프로젝트에 포함된 코드에서는 null을 다음과 같이 허술하게는 사용하지 않을 것입니다. 그래서 우리는 오히려 프로젝트 코드에서 null이 일으킬 수 있는 오류를 잡아내기 더 힘이 듭니다.

Typescript에서는 tsconfig파일에 strictNullChecks옵션을 활성화시켜준다면 IDE에서 위 코드에 에러를 발생시킵니다. 이처럼 우리는 강력한 null 체크를 통해 예상치 못한 오류코드를 방지할 수 있게 됩니다.

 

 

React "Error Boundary"

리액트에서는 예외들을 어떻게 처리해줄까요?

리액트에서는 버전 16부터 Error boundary라는 컴포넌트를 제공함으로서 에러를 효과적으로 처리해줄 수 있습니다.

 

리액트 16부터는 Error Boundary에서 포착되지 않은 에러들이 발생하게 되면 리액트 전체 컴포넌트가 마운트 해제됩니다. 따라서 컴포넌트 어느 부분에서 에러가 발생하더라도 화면의 모든 요소들이 렌더링되지 않는 현상이 일어나게 됩니다. 우리는 대신에 폴백 UI를 제공하고 효율적으로 에러를 포착하기 위해 Error Boundary를 사용하게 되는데요.

 

import React, { Component, ErrorInfo, ReactNode } from "react";
​
interface Props {
  children: ReactNode;
}
​
interface State {
  hasError: boolean;
}
​
class ErrorBoundary extends Component<Props, State> {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
​
  handleReset = () => {
    this.setState({ hasError: false });
  };
​
  public static getDerivedStateFromError(_: Error): State {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }
​
  public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error("Uncaught error:", error, errorInfo);
  }
​
  public render() {
    if (this.state.hasError) {
      return (
        <>
          <h2>Sorry.. there was an error</h2>
          <button onClick={this.handleReset}>Error reset</button>
        </>
      );
    }
​
    return this.props.children;
  }
}
​
export default ErrorBoundary;
​
// 참조 : https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/error_boundaries
// reset 기능 등 일부 코드 확장

[ CodeSandbox Error Boundary 예제 ]

Error Boundary 컴포넌트 코드 예제입니다.

우선 Error Boundary 컴포넌트는 getDerivedStateFromError, componentDidCatch와 같은 생명주기 메소드를 활용해야 하기 때문에 클래스 컴포넌트로 작성되어야 합니다. 또 별도로 에러 상태를 리셋해주는 인터페이스는 제공해주고 있지 않기 때문에 간단하게 reset 기능을 확장 구현해줄 수 있습니다.

 

<ErrorBoundary>
  <ErrorTest />
</ErrorBoundary>

에러를 포착할 컴포넌트 상위에 Error Boundary 컴포넌트를 감싸게 되면 하위 컴포넌트 어느 위치에서든 에러가 발생시 탐지하고 폴백 UI를 렌더링하게 됩니다.

 

Error Boundary를 통해 알 수 있는 React 패러다임

앞에서 언급했듯이 리액트 16버전 이후로는 에러 경계에서 포착되지 않은 에러는 전체 컴포넌트 마운트를 해제시킵니다. 이는 페이스북에서 에러로 손상된 UI는 렌더링되는 것보다 완전히 제거하는 것이 더 옳다고 판단한 것인데요. 가령 결제 앱에서 잘못된 금액이 화면에 출력된다면 큰 문제가 됩니다. 그래서 리액트는 Error Boundary를 사용하여 손상된 UI 대신 폴백 UI를 제공하거나 아예 렌더링을 막아버리는 방법을 선택하였습니다.

Error Boudary는 에러 처리 측면에서 리액트가 보다 선언적인 코드 구성을 지향하고 있다는 것을 알 수 있습니다. javascript에서 지원하는 try ... catch문은 명령형 에러 처리 코드를 구성하게 됩니다. 하지만 Error Boundary 컴포넌트는 선언적으로 에러 처리를 정의하고 폴백 UI를 제공하게 됩니다. 물론 렌더링 과정에 발생하지 않는 이벤트 핸들러와 같은 코드에서 우리는 try ... catch문을 활용해서 에러처리를 작성할 수 있습니다.

 

 

LIST

'Clean Code' 카테고리의 다른 글

Clean Code "주석"  (0) 2022.04.10
Clean Code "Function"  (0) 2022.04.01
Clean Code "의미있는 네이밍"  (0) 2022.03.22