티스토리 뷰

반응형

 

본 포스팅은 코어 자바스크립트 > 에러 핸들링 페이지를 참고하여 제 생각을 덧붙여 작성한 글입니다.

# try...catch

try...catch 문법은 대부분의 프로그래밍 언어에서 에러를 핸들링 하기 위해 쓰이고 있다. 자바스크립트도 예외는 아니다. 문법은 아래와 같다.

try {
  // 에러 발생하면 코드 중단되고 catch 블록으로 넘어감.
  throw new Error('에러 발생');
  alert('실행될 수 없어'); // 실행 안됨.
} catch (err) {
  // 에러 핸들링
  console.log(err);
}

위 코드에서는 명시적으로 에러 객체를 생성해서 던져주었지만, 보통 에러가 발생하면 자바스크립트가 에러 상세내용이 담긴 객체를 생성하여 catch 블록에 인자로 넘겨준다.

 

## 선택적 catch 바인딩

최신 문법

try {
  // ...
} catch { // <-- (err) 없이 쓸 수 있음
  // ...
}

 

# 에러 객체

에러 객체는 다음과 같은 프로퍼티를 갖는다.

  • message – 사람이 읽을 수 있는 형태의 에러 메시지
  • name – 에러 이름을 담은 문자열 (에러 생성자 이름)
  • stack – 표준이 아니지만 대부분의 호스트 환경이 지원하는 프로퍼티로 에러가 발생한 순간의 스택을 나타냄

 

# try...catch...finally

try 블록 안에서 에러 가능성이 있는 코드를 작성하고 해당 코드에서 에러가 발생하면 밑에 있던 코드는 실행되지 않고 catch 블록이 실행된다. 그래서 에러 발생 여부에 상관없이 실행되어야만 하는 코드가 있다면 try...catch...finally 구문을 사용하면 된다.

 

사실 나는 자바스크립트에도 finally 구문이 있다는 것을 이번에 처음 알았다. 그동안 내가 예외처리에 얼마나 소홀했는지ㅠㅠ

finally 구문은 생각보다 굉장히 유용하다. 문법은 아래와 같다.

try {
  // ...
} catch (err) {
  // 에러 핸들링
} finally {
  // 항상 실행
  // 에러 없을시 : try -> finally
  // 에러 발생시 : try -> catch -> finally
}

에러 여부에 상관없이 항상 실행되는 구문인데 내가 가장 유용하다고 생각한 부분은 return 을 사용해 해당 구문을 빠져나가는 경우에도 실행된다는 점이다.

try {
  return true;
} catch (err) {
  // ...
} finally {
  // try 블록에서 return 으로 값이 반환되기 전에 실행된다.
}

finally는 실행 흐름이 함수를 떠나기 전에 실행되기 때문에, 현 함수에서 에러처리를 하고 싶지 않고 스크립트가 죽더라도 확실히 실행되기를 원하는 코드가 있다면 catch 없이 try...finally 구문으로도 쓰일 수 있다.

 

# window.onerror

window.onerror 이벤트는 전역에서 catch를 할 수 있는 방법이다. 문법은 아래와 같다.

window.onerror = function(message, url, line, col, error) {
  // message: 에러 메세지
  // url: 에러 발생한 스크립트 URL
  // line, col: 에러 발생한 줄과 열 번호
  // error: 에러 객체
};

이 이벤트는 예기치 못한 에러가 발생하면 호출되고 이것으로 죽은 스크립트를 복구할 수는 없지만 개발자에게 에러 발생 로그를 찍어 알려줄 수 있다.

따라서 많은 에러 로깅 서비스들이 커스텀 window.onerror 함수를 설정하여 에러가 발생할 때 마다 네트워크 요청을 통해 에러를 기록해준다.

우리 회사도 센츄리라는 서비스를 이용하고 있고 슬랙에 연동해서 에러가 발생할 때 마다 슬랙 알람으로 알려준다.

 

# 커스텀 에러와 에러 확장

특정 에러가 발생했을 때 특정한 처리를 해주어야 하는 경우나, 또는 다양한 에러를 감싸서 추상화 하기 위해 커스텀 에러 객체를 만들 수 있다.

class ReadUserError extends Error {
  constructor(message, cause) {
    // super() 호출을 통해 부모 클래스에서 생성하는 프로퍼티들을 상속받는다.
    super(message);
    this.name = 'ReadUserError';
    this.cause = cause;
  }
}

위와 같이 Error 객체를 상속받아 커스텀 에러를 만들어서 에러 발생이 예상되는 곳에서 커스텀 에러를 던져주면 최상위 호출자에서는 에러 종류를 일일히 파악하거나 분기처리할 필요 없이 커스텀 에러 하나만 처리하면 된다.

function readUser(json) {
  let user;

  try {
    user = JSON.parse(json);
  } catch (err) {
    if (err instanceof SyntaxError) {
      // 내장 에러 객체
      throw new ReadUserError("Syntax Error", err);
    } else {
      throw err;
    }
  }
  
  try {
    validateUser(user);
  } catch (err) {
    if (err instanceof ReferenceError) {
      // 커스텀 에러 객체
      throw new ReadUserError("Validation Error", err);
    } else {
      throw err;
    }
  }
}

try {
  readUser('{잘못된 형식의 json}');
} catch (e) {
  // 추상화 된 에러객체 하나만 처리
  if (e instanceof ReadUserError) {
    alert(e);
    // Original error: SyntaxError: Unexpected token b in JSON at position 1
    alert("Original error: " + e.cause);
  } else {
    // 미확인 에러 던지기
    throw e;
  }
}

이렇게 여러 종류의 에러를 하나의 에러로 추상화하는 것을 '예외 감싸기(wrapping exception)' 라고 한다.

얼마 전 클린코드 책 스터디를 통해서도 위와 같은 로우 레벨에서의 감싸기 작업을 통해 비즈니스 로직과 오류 처리가 잘 분리된 좋은 코드가 나온다는 점을 배웠다.

 

## 과연 클린코드일까?

그러나 개인적으로는 위의 코드는 리팩토링이 필요한 코드라고 생각한다. 물론 에러 핸들링 설명을 위한 단순 예시 코드라서 당연히 좋은 코드는 아니겠지만, 맨 마지막에 readUser()를 호출하는 과정에서 catch에 잡힌 미확인 에러는 또 다시 외부로 던지게 된다. 그러면 상위 로직에서 계속해서 try...catch 구문으로 에러를 처리하는 코드를 작성해야 하고 그러면 코드가 장황해질 수 밖에 없다.

 

앞서 언급한 '클린코드' 책에도 이러한 문구가 나온다.

"오류를 분류하는 방법은 수없이 많지만, 오류를 정의할 때 프로그래머에게 가장 중요한 관심사는 오류를 잡아내는 방법이 되어야 한다."

이 구절을 설명하면서 위의 예외 감싸기 방법을 설명해준다.

 

즉 대부분의 코드는 예외 종류와 상관없이 대응하는 방식은 거의 동일하기 때문에 감싸서 한번에 처리가 가능하다는 뜻이다. 그러나 이것은 강타입 언어에서 에러 객체 타입을 명시해줘야 하기 때문에 하나로 감싸는 방식을 취하는 것이라 생각한다. 위 예시 코드를 설명해놓은 '코어 자바스크립트' 사이트에도 '에러 감싸기' 기법은 객체 지향 프로그래밍에서 널리 쓰이는 패턴이라고 설명하고 있다.

 

그래서 내 생각엔 자바스크립트는 타입 명시가 필요하지 않기 때문에 어떠한 종류의 에러가 발생하든 상위 단계에서 예상하는 값의 디폴트 값을 반환해주면 굳이 에러 감싸기나 미확인 에러를 던지는 코드 없이도 에러 핸들링이 가능하다고 생각한다.

지극히 내 경험상 (미천한 경험치...) 비추어본 생각이기 때문에 언제든 다른 의견 및 조언 환영합니다.★

 

아무튼 위 코드를 다시 정리해보면 아래와 같이 굳이 한번 더 감싸지 않고 처리하는 것이 더 효율적이지 않을까 하는 생각이 든다.

try {
  readUser('{잘못된 형식의 json}');
} catch (e) {
  // 특수 처리해야할 에러 타입이 없다면
  console.log(e);
  return {};
}

 

하지만 물론 외부 라이브러리나 API를 이용하거나 할 경우에는 내부에서 발생하는 에러를 예상할 수 없기 때문에 커스텀 에러를 만들어 예외 감싸기를 하는 작업이 분명 필요할 것이다.

그리고 예외 감싸기는 차치하더라도 커스텀 에러 객체를 만드는 것과 그것을 확장하는 방법은 분명 유용해 보인다. 솔직히 평소에 한번도 커스텀 에러를 만들어 사용해 본 적이 없었는데 이 포스팅을 작성하면서 많은 공부가 되었고 기회가 된다면 꼭 한 번 적용해보고 싶다.

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함