우테코

에러 처리 방식에 대한 고민

prefer2 2022. 10. 30. 15:55

 

 

프로젝트 규모가 커짐에 따라 에러 처리의 중요성을 느끼고 구조 정리와 리팩토링이 필요성이 보였다. 왜 에러 처리의 중요성을 느꼈는지, 어떻게 일관성있게 정리하고 어떻게 에러를 보여줄지를 고민한 과정을 기록해보려고 한다.

 

 

기존의 에러 처리 방식


[왼쪽] axios를 사용하여 api요청을 정리, [오른쪽] react-query를 사용하여 요청

우선 프로젝트에서 어떤 방식으로 api를 관리하고 있는지 살펴보자. '내편' 프로젝트에서는 react-queryaxios를 사용하여 서버 요청들을 관리하고 있다. 이 두 조합을 사용하며 느낀 장점은 코드의 분리를 확실하게 할 수 있다는 점이었다. axios를 사용하여 api 요청 함수들을 도메인별로 정의해놓고, react-query의 useQuery와 useMutation에서 이 api 요청 함수들을 사용했다(use + CRUD + api명 형식으로 커스텀 훅을 만들어 정의하였다)

 

다만 이렇게 훅을 분리할 때 에러 처리 방식이 일관되지 않았다. 어떤 커스텀 훅에서는 onError로 에러 처리 로직을 실행했고, 어떤 커스텀 훅에서는 isError를 반환하여 해당 에러에 대한 UI를 보여주고 있었다.

onError: (error) => {
        if (axios.isAxiosError(error) && error.response) {
          const customError = error.response.data as CustomError;
          openSnackbar(customError.message);
      }
},
if (isError) {
    return (
      <Modal onClickCloseButton={onClickClose}>
        <StyledInvite>
          <StyledHeader>모임 초대하기</StyledHeader>
          <div>초대링크 생성에 실패했습니다. 다시 시도하세요. 😥</div>
        </StyledInvite>
      </Modal>
    );
  }

 

위와 같은 방식에는 두가지 문제점이 있었다

1️⃣ 어디서 에러를 처리해주고 있는지 확인하기 힘들다. 디버깅이 어렵다
2️⃣ 중복되는 코드가 많다. openSnackbar나 로그아웃 등을 반복해서 작성했다

문제의 원인은 분산된 에러 처리였다. 각각의 커스텀 훅에서 에러를 처리해주고 있었기 때문에 에러의 원인을 찾으려면 해당 훅을 확인해야 했고, openSnackbar와 같은 로직을 반복해서 작성해야 했다. 

 

 

에러를 정리해보자


 

REST DOCS에 잘 정리되어있는 에러코드들을 보며 어떤 방식으로 에러를 정리해주어야 할지 고민했다. 사용자에게 보여줄 에러보여주지 않아도 되는 에러, 또 각 에러의 경우 어떻게 처리하는 것이 좋을지 나눠보았다.

사용자에게 보여주는 경우를 나눠 보면 아래와 같다

1️⃣ Snackbar로 에러 메시지 보여주기
2️⃣ 페이지 이동하기(허용되지 않는 페이지에 대해)
3️⃣ 에러 페이지 보여주기

 

 

어떻게 에러 처리를 할 것인가?


결론부터 말하자면 우리가 알고 있는, 에러 처리를 결정한 에러의 경우에는 react-query의 defaultOption의 onError로 처리하였고, 500번대의 서버에러와 예상하지 못하는 에러의 경우에는 errorBoundary로 처리하였다.

에러를 처리하는 방법에는 errorBoundary에서 HOC를 사용하여 모든 에러를 처리해주는 방법도 있고, react-query의 defaultOption의 onError로 모든 에러를 처리해주는 방법도 있고 또 이 두개를 적절하게 섞어서 사용하는 방법도 있다. 각각의 방법에 장단점이 있기에 우리 프로젝트에서는 어떤 방법이 더 적절한지 그리고 왜 이런 방법을 사용하고자 했는지 오랫동안 고민했다.

많은 고민 끝에 react-query의 defaultOption의 onError와 errorBoundary를 사용하여 일관된 에러 처리를 해주기로 했다. 초기에 setDefaultOptions를 통해 작성한 error handler를 설정했다. error handler의 경우 useSnackbar라는 훅을 사용하기 위해 hook으로 작성하였다.

모든 에러에 대해서 처리해 줄 수 있다면 좋겠지만 때로는 예상하지 못하는 에러가 발생하기도 한다. 500번대 에러 같은 서버에러의 경우에도 사용자가 자세하게 알 필요가 없는 에러라고 생각했다. 이 아티클을 읽고 해당 에러들을 처리해주기 위해 errorBoundary를 사용하였다. 

switch (errorCode) {
      // OAuth authorizationCode 또는 redirectUri가 잘못된 경우, 혹은 카카오의 Authorization Server가 정상작동하지 않는 경우
      case 3014:
        navigate("/login");
        openSnackbar(message);
        break;
      // 이미 가입한 멤버가 모임에 재가입 시도
      case 4007:
      case 4008:
        navigate(`/team/${requestUrlPaths[teamIdIndex]}`);
        openSnackbar(message);
        break;
      // 초대코드 관련
      case 4015:
      case 4016:
      case 4017:
        navigate(`/team/${requestUrlPaths[teamIdIndex]}`);
        openSnackbar(message);
        break;
      default:
        openSnackbar(message);
    }
queryClient.setDefaultOptions({
    queries: {
      useErrorBoundary: (error) => {
        const axiosError = error as AxiosError<ApiErrorResponse>;
        const errorStatus = axiosError?.response?.status;
        return !!errorStatus && errorStatus >= 500;
      },
      onError: (err) => errorHandler(err as AxiosError<ApiErrorResponse>),
      retry: 0,
    },

더 자세한 코드는 내편 github에서 확인 가능하다( apiHandler 코드, queryClient 설정 코드)

 

 

에러 처리의 중요성


예를 들어 페이스북 메신저는 사이드 바, 정보 패널, 대화 기록과 메시지 입력을 각각 별도의 에러 경계로 감싸두었습니다. 이 UI 영역 중 하나의 컴포넌트에서 충돌이 발생하면 나머지 컴포넌트는 대화형으로 유지됩니다. 

React 공식 문서 - error boundary

사실 가장 마음이 가던 방식은 이 아티클에서 제안하는 방식이었다. 의미있는 컴포넌트 단위로 ErrorBoundary와 Suspense를 설정하고, 적절한 로딩과 fallback UI를 보여주는 방법이다. 배너에 에러가 발생했을 때 페이지 전체가 에러페이지가 되는 것보다 배너에만 에러를 보여주고 나머지 기능을 사용할 수 있도록 하는 것이 더 좋을 것이다. 가장 선언적이고 관심사 분리가 잘 된 방법이라고 생각된다. React 공식 문서에서 제안하는 방법과 동일한 방법이라는 생각도 든다.

 

에러 처리를 정리하며 코드도 간결해지고 사용성도 좋아지는 경험을 했다. 코드가 정리되니 사용자가 어떤 플로우로 에러를 경험해야하는지 개발을 하는 입장에서도 정리가 되었다.

이전까지는 작은 프로젝트만 진행하다 보니 에러처리의 중요성을 전혀 알지 못하고 있었다. 사용자가 없는 프로젝트였기에 alert나 console로 에러를 확인하기만 했다. 사용성을 위한 에러처리를 고민하다 보니 초기의 구조가 굉장히 중요하겠구나라는 생각이 든다. 어떤 방식으로 에러를 보여주고 처리해줄지를 고민하면 프로젝트 구조는 알아서 따라오겠구나 라는 생각이 들기도 한다. 

현재 프로젝트에서 적용할 수 있는 최선의 에러 처리 방식을 적용했다고 생각된다. 위와 같은 고민 과정을 겪은 덕분에 앞으로 작업을 진행할 때 에러라는 고민 요소를 추가할 것이다. 

 

 

반응형

'우테코' 카테고리의 다른 글

Refresh Token 도입기  (0) 2022.10.23
프론트엔드 성능 최적화 - 2  (2) 2022.09.12
프론트엔드 성능 최적화 - 1  (0) 2022.09.06
내편 UI 개발기  (6) 2022.09.02
[Level 2] 미션 1: 페이먼츠 1, 2단계  (0) 2022.05.16