에러를 확인하고 다루기

윤슬기·2020년 10월 4일
1
post-thumbnail

첫 회사에서 일을 시작한 지 네 달이 되어간다. 여러 사람과 협업하고 이야기하면서 잘 모르는 세계에 대해 배우고 있다.

입사하기 전에 만들던 프로젝트와 회사 일을 비교했을 때, 가장 다른 부분은 내가 관여하는 서비스에 '실제 사용자'가 있다는 점이다. 다양한 경로로 서비스를 제공하는 곳인지라 코드를 고치고 적어넣을 때마다 여러 가지 모습으로 화면을 들여다볼 사람들을 상상한다(그리고 약간 식은땀을 흘린다).

실제 사용자가 있어 그 무게를 남다르게 느낀 건 '에러를 처리하고 에러에 대비하는 방법'이다. 스크립트를 읽다가 문제가 생겨 화면이 하얗게 멈추거나, 적절한 방법으로 에러 상황을 알리지 않아 사용자가 그저 계속 버튼을 클릭하게 된다면 서비스에도 고객에게도 큰 문제이다. 테스트 코드를 작성하거나, 빈틈없이 문서를 작성하는 환경이라면 보다 안전하겠지만 그렇지 않은 곳도 있다.

에러 정의 확인 : 우선 서버 개발자와 이야기하기

유지보수를 거치면서 바뀐 API 정보가 문서 등에는 반영되지 않은 경우도 더러 있기 때문에, 데이터 요청과 관련된 일을 맡았다면 우선 어떤 에러를 어떤 response와 함께 서버에서 보내는지 확인한다. 기획단계에서 미처 정의되지 않은 부분을 세세하게 들여다보고 동료 개발자들과 상의한 후 해당 에러를 어떤 방식으로 처리할지 생각한다.

에러 가능성 낮추기 : '무조건 정해진 그 위치'에서 값을 가져오지 않기

데이터를 사용하려면 대체로 데이터에서 원하는 값을 찾아야 한다. 데이터가 항상 무결하고 완벽하게 만들어져 있다면 원하는 값은 항상 그 자리에 있겠지만, 그렇지 않을 때도 있다.

이전에 다룬 값 중에서는 입력 시 기계적인 검증 과정 없이, 사람이 자체적으로 룰을 지키며 하나씩 손으로 입력한 데이터가 있었다. 나는 그 데이터가 공통으로 가진 날짜를 데이터 입력 날짜로 이용하려고 했다. 간단하게 재현하자면 이런 모양의 데이터다.

const meals = ['2020-01-01,치즈돈까스,8000', '2020-01-01,초콜릿,2000'];

나는 우선 배열 속 각 문자열을 쉼표를 기준으로 배열로 만든 후, 그 배열의 0번째 값을 날짜로 사용했다.

const meals = [['2020-01-01','치즈돈까스','8000'], ['2020-01-01','초콜릿','2000']];
const day = meals[0][0]; // '20200101'

몇 가지 값에서는 문제가 없었다. 하지만 몇백 개의 값으로 테스트를 해 보니 잘못 입력된 값들이 더러 발견되었다.

const meals = [['2020-0','1-01치즈돈까스','8000'], ['2020-01-01','초콜릿','2000']];
const day = meals[0][0]; // '20200' !!

데이터를 입력하는 과정 중 잘못된 값을 입력하지 못하도록 검증하는 장치가 마련되어 있어야 하겠지만, 예상치 못한 실수는 항상 존재할 수 있다. 그래서 정해진 자리에 원하는 값이 없을 수 있음을 상정하고, 최대한 '조건을 만족하는 값'을 때마다 찾아 사용하는 방향으로 방법을 바꾸었다.

// 숫자로 넘어온 문자열이 유효한 날짜인지 검증하는 함수
function getValidDate (date) {
  return !isNaN(new Date(date));
};

// meals에서 유효한 날짜 찾기
const validDate = meals.find(array => getValidDate(array[0]));
// 유효한 날짜가 있다면 그 날짜를 사용하고, 아니라면 데이터가 생성된 날짜인 createdAt 값을 사용한다
const reviewCreatedDate = data.created_at;
const originalDate = validDate && validDate[0] || reviewCreatedDate;

에러 다루기 : error callback 활용

데이터 요청 시 에러가 발생하는 상황을 감지하고 처리하는 방법은 여러 가지가 있다. 그중 동료에게 배운 방법은 에러 상황에서 사용할 함수를 데이터 요청 함수에 인자로 넘기는 방법이다. 이는 간단한 요청이든 미들웨어를 이용한 조금 더 복잡한 로직에서든 비슷하게 사용할 수 있다.

먼저 에러 시 평가될 함수를 만든다.

const errorCallback = (error) => {
  alert('에러가 발생했습니다!');
}

그리고 API 요청을 다루는 함수를 작성할 때, 여러 인자를 받도록 만든다. 받는 인자는 요청 상세 정보, 요청 시 함께 전송하는 파라미터, 성공 시 평가될 함수와 에러 시 평가될 함수다.

API 요청은 Promise 객체로 작성하여, thencatch로 성공 및 에러 상황을 다룬다. try... catch 문법을 사용할 수도 있다.

// promise chaining
function request({apiCall, params, successCallback, errorCallback}) {
  apiCall(params)
    .then(successCallback)
    .catch(errorCallback);
}

// try...catch
async function request({apiCall, params, successCallback, errorCallback}) {
  try {
    const response = await apiCall(params)
    successCallback(response);
  } catch (error) {
    errorCallback;
  }
}

위의 함수를 호출할 땐 아래처럼 하나의 객체로 인자를 정리해 넘기면 읽기 쉽다. 아래 예시에서는 데이터 요청에 성공했을 때 홈 화면으로 이동하고, 에러가 발생하면 창을 닫는다.

const requestPayload = {
  apiCall: (param) => fetch(url, 'POST', param),
  params: params,
  successCallback: () => {
    history.push("/home");
  },
  errorCallback: () => {
    window.close();
  },
};

request(requestPayload);

일하면서 예기치 못한 에러를 몇 번 마주하고 나서부터는, 최대한 미리 에러가 생길 장면을 예측하고 대비하게 되었다. 하지만 수동으로 이루어지는 테스트 방법들은 가끔 상상력이나 창의력을 시험하는 기분이 들 때도 있고, 반복 작업으로 시간이 오래 걸리거나 종종 에러 상황을 발생시키기 어렵기도 하다. 팀 사정에 따라 바로 도입하기 어렵지만, 에러가 발생할 수 있는 임의의 환경을 갖추고 테스트를 자동화할 수 있는 툴들을 들여다보려고 한다.

profile
👩🏻‍💻

0개의 댓글