[TypeScript] Try-Catch문 속 error의 타입에 대해 (Typing Errors in a Try-Catch)
[TypeScript] Try-Catch문 속 error의 타입에 대해 (Typing Errors in a Try-Catch)

[TypeScript] Try-Catch문 속 error의 타입에 대해 (Typing Errors in a Try-Catch)

Tags
TypeScript
Published
December 20, 2023
Author
Chaehyeon Kim

🍄 throw 문 (Exception throwing)

  • Javascript(TypeScript)의 throw statement는 명시적으로 예외를 발생시킬 때 사용된다.
  • throw문에 의해 exception이 발생되면 정상적인 실행 플로우를 중단하고, 제어의 흐름을 가장 가까운 try...catch 문으로 이동시켜 예외를 처리하게 된다.
  • 현재 함수 내부에서 매칭되는 catch 블록이 없다면 exception은 상위 호출 스택으로 propagate되어 외부 함수에 있는 try…catch 문으로 전달된다.
  • TypeScript에서는 어느 타입의 값이든 예외로 던질 수 있다.
    • 원시타입 : number, string, boolean, symbol..
    • objects, arrays, functions, classes, Error 또는 TypeError 같은 custom type
 
 

🍄 Error type annotations

try { ... throw new Error("Failed!"); } catch (error: 항상 any 또는 unknown) { ... }
  • TypeScript는 대부분의 코드에서 정적 타입 체크를 제공하지만, exception은 런타임 오류로 간주된다.
    • throw된 예외값의 타입이 런타임에 결정되므로, TypeScript 컴파일러는 throw된 값에 대해 특정 타입을 강제하지 않는다.
    • 즉, catch 문에 전달되는 error 매개변수에 대한 타입 설정은 any 또는 unknown으로만 제한된다.
    •  
      이렇게 error 매개변수에 다른 타입을 지정하면 위와 같이 에러를 발생시킨다.
      이렇게 error 매개변수에 다른 타입을 지정하면 위와 같이 에러를 발생시킨다.
       
 
그렇다면, catch문 내부의 error에 대해서 어떻게 타입 정의를 해야 바람직할까?

[방법1]

  • any
    • error 매개변수의 타입으로 : any 를 지정
    • 따로 명시하지 않더라도 기본값으로 사용된다.
    • catch문 내부에서 type checking 없이 어느 매개변수든 접근 가능하게 된다.
    • 예상했던 프로퍼티가 에러 객체에 전달되지 않았을 경우, 런타임 에러를 유발할 수 있으므로 안전한 방법은 아니다.

[방법2] 👍

  • unknown
    • error 매개변수의 타입으로 : unknown 을 지정
    • error 객체 타입이 unknown으로 지정된 경우, catch문 내부에서 error 객체에 접근하기 전에 반드시 명시적인 type checking과 type narrowing이 요구된다.⭐️
    • 즉, 먼저 type narrowing으로 Error의 타입을 구체화한 후 해당 에러 객체의 프로퍼티에 접근하게되므로, 런타임 에러를 방지하는 보다 더 안전한 방식이라고 할 수 있다.
    • 예시 코드
    • try { ... throw new Error("Failed!"); } catch (error: unknown) { if (typeof error === "string") { // error 객체 타입이 문자열인 경우 ... } else if (error instanceof Error) { // error 객체가 Error의 인스턴스인 경우 ... } else { // 그 외의 경우... } }
    • tsconfig 옵션으로 useUnknownInCatchVariables 를 활성화하면 : unknown 처럼 직접적인 명시 없이도 에러의 타입을 Unknown으로 지정할 수 있다.
 

[방법3]

  • Type Assertion(타입 단언)
    • try { if (state === "fail") { throw new Error("Failure!"); } } catch (error) { return (error as Error).message; }
    • error가 Error객체 등 특정한 타입임을 강제하는 방식
    • 코드가 길어지는 경우, 항상 동일한 타입의 exception을 throw하지 않을 수도 있기 때문에 완벽한 방식은 아니다.
      • 예) 위 예시 코드의 try문 내부 어딘가에서 string 타입의 에러를 throw하는 경우, catch문에서 undefined를 리턴하게 된다!
 
 

🍄 built-in Error class (내장 Error 클래스)

  • 내장 Error 클래스
    • 오류 객체를 표상하는 JavaScript의 표준 클래스이다.
    • Property
      • name : 에러의 유형을 나타내는 프로퍼티
      • message : 사람이 이해 가능한 언어로 서술된 에러의 내용. 에러 메시지
      • stack : 에러가 발생한 지점을 추적할 수 있는 스택을 담고 있는 프로퍼티
      try { // some code that may throw an error throw new Error("Random error message"); } catch (err) { // handle the error if (err instanceof Error) { console.log(err.name); // the type of error console.log(err.message); // the description of the error console.log(err.stack); // the stack trace of the error } else { // handle other errors } }
 
 

🍄 Standard error classes (표준 Error 클래스)

  • built-in(내장) Error 클래스로부터 파생된 JavaScript의 표준 Error 클래스
  • 종류
    • SyntaxError : 자바스크립트 코드 구문 분석시 발생하는 구문(syntax) 오류
    • TypeError : 호환되지 않는 type에서 작업 또는 함수가 수행되었을 때의 오류
    • ReferenceError : 정의되지 않은 변수나 함수에 대한 잘못된 참조 오류
    • RangeError : 숫자 값이 허용 범위를 벗어난 경우의 오류
    • URIError : encodeURIComponent 혹은 decodeURIComponent 등의 인코딩, 디코딩 기능에서 잘못된 URI 문자열이 발견될 경우의 오류
 
 

🍄 Custom Error types

  • 내장 Error 클래스를 확장하여 커스텀 에러를 생성할 수 있다.
  • message 프로퍼티를 지정하기 위해 자식 생성자에서 super()를 호출해야 하며, name 프로퍼티 또한 명시적으로 세팅해주어야 한다.
  • 예시
    • /********** 예시 1 ************/ class CustomError extends Error { constructor(message: string) { super(message); // call the parent constructor this.name = "CustomError"; // set the name property } } try { throw new CustomError("Something went wrong"); } catch (err) { if (err instanceof CustomError) { console.log(err.name); // CustomError console.log(err.message); // Something went wrong } else { // handle other errors } } /********** 예시 2 ************/ // Custom Error Class에 추가 프로퍼티를 설정할 수 있다. class HttpError extends Error { statusCode: number; constructor(statusCode: number, message: string) { super(message); this.name = "HttpError"; this.statusCode = statusCode; } log() { console.log(`Http error ${this.statusCode}: ${this.message}`); } } try { throw new HttpError(404, "Not found"); } catch (err) { if (err instanceof HttpError) { err.log(); // Http error 404: Not found } else { // handle other errors } }
 
  • 사용자 정의 에러 타입 ⇒ 에러의 구체적인 특성을 전달하는 의미있는 에러명 정의가 가능하다.
  • 즉, 코드 명확성 및 가독성이 향상되어 코드를 이해하고 유지보수하기에 더 수월해진다.
 
 

🍄 Best practices for error handling

마지막으로, 타입스크립트에서의 바람직한 오류 처리 방법론에 대해 정리해보면 다음과 같다.
  1. Error의 성격을 정확히 나타내는 Error type을 선택할 것. (TypeError, SyntaxError, 사용자 정의 Error 등..)
  1. Error에 대한 유용한 정보를 제공하는 설명적인 오류 메시지를 포함할 것.
  1. 처리되지 않은 예외로 인한 프로그램 충돌을 피할 것.
  1. 에러가 조용히 무시되거나 숨겨지는 상황을 피할 것.
  1. 좋은 사용자 경험을 보장하는 방식으로 예외를 처리할 것. Error 발생 이후에도 프로그램이 계속 실행되거나 오류로부터 복구될 수 있도록 대체 메커니즘/경로를 고려해두자.
  1. 중앙 집중식 오류 처리 메커니즘을 만들거나, 오류 처리 기능을 제공하는 프레임워크 혹은 라이브러리를 활용할 것. Error를 포착 및 추적하기 위한 견고한 로깅 및 모니터링 메커니즘을 구현하라.
  1. 예상되는 Error 조건과 이러한 Error가 발생했을 때 코드의 동작을 모두 테스트할 것.
  1. TypeScript의 정적 타입 검사를 활용해 컴파일 시점에 오류를 포착할 것.
 
 
 

참고