👶 TypeScript

에러 처리 - 예외 반환

개발자 린다씨 2023. 1. 23. 12:00
반응형

예외 반환

TypeScript는 자바가 아니며 throws 문을 지원하지 않습니다.

참고로 throws 문은 메서드가 어떤 종류의 런타임 예외를 발생시킬 수 있는지 알려주어서 해당 메서드의 사용자가 발생 가능한 에러를 적절하게 처리할 수 있도록 도와줍니다.

 

하지만 유니온 타입을 이용해 비슷하게 흉내 낼 수 있습니다.

function ask() {
    return prompt("생일 언제임?");
  }
  
// 커스텀 에러 타입
class InvalidDateFormatError extends RangeError{}
class DateISInTheFutureError extends RangeError{}

/**
 * 
 * @throw {InvalidDateFormatError} : 사용자가 생일을 잘못 입력함
 * @throw {DateISInTheFutureError} : 사용자가 생일을 미래 날짜로 입력함
 */

  function parse(birthday: string): Date | InvalidDateFormatError | DateISInTheFutureError {
    let date = new Date(birthday);
    if (!isValid(date)) {
      throw new InvalidDateFormatError("YYYY/MM/DD 로 입력하셈여");
    }
    if(date.getTime() > Date.now()){
        throw new DateISInTheFutureError('타임로드 했닝?')
    }
    return date;
  }

이제 이 메서드 사용자는 모든 세 가지 상황을 처리해야 하며 그렇지 않으면 컴파일 타임에 TypeError가 발생합니다.

function ask() {
    return prompt("생일 언제임?");
  }
  
// 커스텀 에러 타입
class InvalidDateFormatError extends RangeError{}
class DateISInTheFutureError extends RangeError{}

/**
 * 
 * @throw {InvalidDateFormatError} : 사용자가 생일을 잘못 입력함
 * @throw {DateISInTheFutureError} : 사용자가 생일을 미래 날짜로 입력함
 */

  function parse(birthday: string): Date | InvalidDateFormatError | DateISInTheFutureError {
    let date = new Date(birthday);
    if (!isValid(date)) {
      throw new InvalidDateFormatError("YYYY/MM/DD 로 입력하셈여");
    }
    if(date.getTime() > Date.now()){
        throw new DateISInTheFutureError('타임로드 했닝?')
    }
    return date;
  }
  
  // 입력한 날짜가 유효한지 검사
  function isValid(date: Date) {
    return (
      Object.prototype.toString.call(date) === "[object Date]" &&
      !Number.isNaN(date.getTime())
    );
  }
  
  let result = parse(ask())
  if(result instanceof InvalidDateFormatError){
    console.error(result.message)
  } else if (result instanceof DateISInTheFutureError){
    console.error(result.message)
  } else {
    console.info('날짜는', result.toISOString)
  }

이상으로 TypeScript는 타입 시스템을 활용하여 다음을 수행했습니다.

  • parse의 시그니처에 발생할 수 있는 예외를 나열했습니다.
  • 메서드 사용자에게 어떤 에러가 발생할 수 있는지 전달했습니다.
  • 메서드 사용자가 각각의 에러를 모두 처리하거나 다시 던지도록 강제했습니다.
let result = parse(ask())
  if(result instanceof Error){
    console.error(result.message)
  }  else {
    console.info('날짜는', result.toISOString)
  }

물론 메모리 부족 에러나 스택 오버플로 예외 등으로 프로그램이 여전히 크래시 될 가능성은 남아있지만 현실적으로 이런 에러는 개발자가 대응할 수 있는 여지가 별로 없습니다.

 

이 방식은 상당히 단순하고 대단한 자료구조도 사용하지 않습니다.

 

하지만 API 사용자에게 실패 유형과 추가 정보를 얻을 수 있는 길을 알려주기에 충분합니다.

 

한편 에러를 던지는 연산을 연쇄적으로 호출하거나 중첩하면 코드가 지저분해진다는 단점이 있습니다.

 

예를 들어 T | Error1을 반환하는 함수를 이용하는 모든 호출자 함수는 두 가지 선택지 중 하나를 고를 수 있습니다.

  1. 명시적으로 Error1을 처리합니다.
  2. T를 처리하고 Error1은 호출자 함수의 사용자가 처리하도록 전달합니다. 하지만 이 방법을 자주 사용하면, 최종 사용자가 처리해야 할 에러의 종류가 크게 늘어날 수 있습니다.
function x(): T | Error1 {
    // 블라 블라
  }

  function y():U | Error1 | Error2 {
    let a = x()
    if(a instanceof Error){
        return a
    } 
    // a로 어떤 동작을 수행
  }

  function z(): U | Error1 | Error2 | Error3 {
    let a = y()
    if(a instanceof Error){
        return a
    }
    // a로 어떤 동작을 수행
  }

이 방식은 조금 복잡한 대신 안전성이 뛰어납니다.

반응형