Error handling

Junghyun Park·2021년 6월 28일
0

배경 및 개요

Error handling은 서비스 수준의 개발에 있어서, 매우 중요
Error handing을 잘 하면, 에러가 났을 경우, 빠르고 쉽게 원인을 파악하고 대처(해결)이 가능

자바스크립트에서의 Error 인스턴스 활용

  • 자바스크립트에는 Error Class가 있어서, 에러처리를 하고자 할 때에는 Error 인스턴스를 생성해서 throw 할수 있음
  • 추가적으로, Error Sub Types를 활용할 수 도 있음
    : RangeError, ReferenceError, SyntaxError, TypeError, URIError
  • 유의할 점은 throw 할 때 Error 인스턴스 생성 없이 곧바로 string 자체를 throw 하지 말 것! (왜냐하면, Error 인스턴스를 생성해야지 자동으로 stack 속성으로 tracking이 가능
    : 반드시 throw만 사용해야할 필요는 없고, Error 인스턴스 객체를 넘겨주기만 하면 됨
try {
  throw new Error('Something bad happened');
}
catch(e) {
  console.log(e);
}
  • typescript로 (함수) 구현하는 경우, 명확하게 value와 return 값을 정의할 것 (아래 1번 X, 2번 O)
# 1
function validate(value: number) {
  if (value < 0 || value > 100) throw new Error('Invalid value');
}
# 2
function validate(value: number): {error?: string} {
  if (value < 0 || value > 100) return {error:'Invalid value'};
}

참고사이트

타입 스크립트로 커스텀 에러 클래스 활용하기

필요성

  • 직접 에러 객체(클래스)를 만들고, 타입가드를 활용하여 에러를 구분하여 타입스크립트 컴파일러의 도움을 받을 수 있음

구현예시

  • 직접 에러 객체(클래스) 만들기
class HTTPError extends Error {
  constructor(statusCode: number, message?: string) {
    super(message) // 반드시 호출해야함
    this.name = `HTTPError`
    this.statusCode = statusCode
  }
}
const fetchPosts = async () => {
  const response = await fetch(`/api/posts`)
  if (response.ok) {
    return await response.json()
  } else {
    throw new HTTPError(response.status, response.statusText)
  }
}
const renderPosts = async () => {
  try {
    const posts = await fetchPosts()
    // Do something with posts
  } catch (e) {
    console.error(e.statusCode) // <- 컴파일 에러
    //instanceof 는 자바스크립트에 내장되어 있는 value가 다른 value의 instance인지를 체크하는 operator
    if (e instanceof HTTPError) {
      alert(`fetching posts failed, error code is ${e.statusCode}`) // 이건 정상
    }
  }
}
  • 에러 객체 내 에러를 처리하는 로직까지 포함시키도록 하기
class CustomError extends Error {
  name: string
  constructor(message?: string) {
    super(message)
    this.name = new.target.name
    Object.setPrototypeOf(this, new.target.prototype)
    Error.captureStackTrace && Error.captureStackTrace(this, this.constructor) // 하는 김에 스택트레이스도 바로잡아줍시다
  }
}
class HTTPError extends CustomError {
  constructor(
    private statusCode: number,
    private errorData: Record<string, any>,
    message?: string
  ) {
    super(message)
    this.name = ‘HTTPError’
  }
  get rawErrorData() {
    return this.errorData
  }
  get codeToErrorMessage() {
    switch (this.statusCode) {
      case 401:
        return `You don’t have a permission.`
      // ...
    }
  }
}
try {
  // ...
} catch (e) {
  if (e instanceof HTTPError) {
    showErrorMessage(e.codeToErrorMessage)
  }
}

참고사이트

Type-safe Error Handling in TypeScript

  • Throw에 의존하지 말기
    : throw는 typesafe가 아니고, 어떤 값이라도 전달하게 되고, 이에 대한 명세(documentation)을 따로 작성하고 일일히 업데이트 해야 하는 불편
type ResponseData = {
  statusCode: number
  responseBody?: ResponseBody
}
const makeHttpRequest = async (url: string): Promise<ResponseData> => {
  if (!isUrl(url)) {
    throw new Error(
      'Invalid string passed into `makeHttpRequest`. Expected a valid URL.'
    )
  }
  // ...
  // other business logic here
  // ...
  return { ... } // ResponseData
}
  • SuccessFailure의 경우로 나누어 미리 형식을 지정해 놓기
    : ok는 T type을 가지는 인자를 받는 함수이고, err는 E type을 가지는 인자를 받는 함수로 정해짐
type Result<T, E>
  = Ok<T, E> // contains a success value of type T
  | Err<T, E> // contains a failure value of type E
// utility functions to build Ok and Err instances
const ok = <T, E>(value: T): Result<T, E> => new Ok(value)
const err = <T, E>(error: E): Result<T, E> => new Err(error)
const makeHttpRequest = async (url: string): Promise<Result<ResponseData, Error>> => {
  if (!isUrl(url)) {
    return err(new Error(
      'Invalid string passed into `makeHttpRequest`. Expected a valid URL.'
    ))
  }
  // ...
  // other business logic here
  // ...
  return ok({ ... }) // Ok(ResponseData)
}

참고사이트

기억할 내용

  • error handling 위해서는 기본적으로 try..catch..를 이용
  • throw를 사용하여 exception 처리하는 경우는 Error 인스턴스 객체를 만들어 넘기기
  • setTimeout의 인자인 함수에서 발생한 에러는 catch할 수 없음(그 함수 내부에 다시 try, catch 문을 삽입해야함)

React Error Boundaries

https://reactjs.org/docs/error-boundaries.html#gatsby-focus-wrapper

배경

  • 리액트에서 자바스크립트 에러를 해결하기 위해, React 16에서 error boundary 컨셉을 도입
  • 일종의 React component로서, child component tree의 어느곳에서 발생하는 자바스크립트 에러를 catch하고, loggin, fallback UI를 보여줌.

    주의사항 (아래의 에러는 catch할 수 없음)

    1. Event Handler
    2. Asynchronouse Code
    3. Server Side rendering.
    4. boundary 그 자신에서 발생하는 에러

클래스 컴포넌트는 그것이 life cycle method인 getDerivedStateFromError() or componentDidCatch()로 정의되면 error boundary가 됨

  • 마치 자바스크립트의 catch block 처럼 행동, but for components
  • 오직 클래스 컴포넌트만 error boundaries가 될 수 있음
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

Error Boundaries는 최상위에 위치할 수도, 개별 child component에 위치할 수도 있음

try/catch 도 좋지만, 오직 imperative code(명령형 코드)에서만 작동하나, Error Boundaries나 리액트의 선언적인 본성(선언형 코드)을 보존하고, 우리가 기대하는 대로 행동할 수 있음 (권장)

Event handler 에러는 error boundaries에서 catch하지 못하므로(왜냐하면, 이 에러는 랜더링할 때 발생하는 에러가 아니므로), try/catch 구문 활용


React Native에서의 Error handling

https://elazizi.com/handling-errors-in-react-native-a-complete-guide

  • 2 종류의 에러가 있을 수 있음
    1. JS or React 자체의 에러
    2. Native Module 측면에서의 에러

1. JS Exception

  • try/catch는 명령형 코드에서만 작동하므로, 선언형 코드인 리액트에서는 공식문서에서 제공하는 error boundaries를 활용할 것
import * as React from "react";
import { ErrorBoundary } from "react-error-boundary";
import { View, StyleSheet, Button } from "react-native";
import { Text } from "components";
const myErrorHandler = (error: Error) => {
  // Do something with the error
  // E.g. reporting errorr using sentry ( see part 3)
};
function ErrorFallback({ resetErrorBoundary }) {
  return (
    <View style={[styles.container]}>
      <View>
        <Text> Something went wrong: </Text>
        <Button title="try Again" onPress={resetErrorBoundary} />
      </View>
    </View>
  );
}
export const ErrorHandler = ({ children }: { children: React.ReactNode }) => (
  <ErrorBoundary FallbackComponent={ErrorFallback} onError={myErrorHandler}>
    {children}
  </ErrorBoundary>
);

2. Native Exception

  • react-native-exception-handler와 같은 라이브러리 사용해서 처리할 수 있음
  • 에러 발생시 alert를 customize하기 위해서는 native code를 수정해야함

3. Tracking Exceptions

  • Sentry와 같은 클라우드 서비스 통해서, 실시간으로 추적할 수 있음
profile
21c Carpenter

0개의 댓글