TypeScript - 에러 처리

euNung·2022년 8월 14일
0

TypeScript

목록 보기
8/8

null 반환

  • 단점
    • 자세한 오류 대신 모호한 에러 메시지를 보냄
    • 조합이 어려워짐
    • 모든 연산에서 null을 확인해야 하므로 연산을 중첩하거나 연결할 때 코드가 지저분해짐
function ask() {
  return prompt('When is your birthday?')
}

// 사용자가 유효한 내용을 입력하면 Date 반환 
// 그렇지 않으면 null 반환
function parse(birthday: string): Date | null {
  let date = new Date(birthday)
  if (!isValid(date)) {
    return null
  }
  return date
}

function isValid(date: Date) {
  return Object.prototype.toString.call(date) === '[object Date]' 
  	&& !Number.isNan(date.getTime())
}

let date = parse(ask())
if (date) {
  console.info('Date is', date.toISOString())
} else {
  console.error('Error parsing date for some reason')
}

예외 던지기

  • null 반환 대신 예외를 던질 때 장점
    • 어떤 문제냐에 따라 대처 가능
    • 디버깅에 도움되는 메타데이터 얻을 수 있음
  • 단점
    • try/catch 구문의 코드가 하나의 파일에 담겨 있고, 나머지 코드를 다른 라이브러리에서 import 하여 사용했을 때, 특정 타입의 에러가 던져질 수 있다는 사실을 알기 어려움
      => 함수 이름에 명시하거나 문서화 주석에 정보를 추가해야함
...

function parse(birthday: string): Date {
  let date = new Date(birthday)
  if (!isValid(date)) {
    throw new RangeError('Enter a date in the form YYYY/MM/DD')
  }
  return date
}

...
try {
  let date parse(ask())
  console.info('Date is', date.toISOString())
} catch (e) {
  // 다른 에러가 발생했을 떄 무시하지 않도록, 처리하지 않은 에러 다시 던지도록 하기
  if (e instanceof RangeError) {
    console.error(e.message)
  } else {
    throw e
  }
}
  • 커스텀 에러 타입 사용(에러를 서브클래싱하여 더 구체적으로 표현)
...
class InvalidDateFormatError extends RangeError { }
class DateIsInTheFutureError extends RangeError { }

/** 문서화 주석
 * @throws {InvalidDateFormatError} 사용자가 생일을 잘못 입력함
 * @throws {DateInTheFutureError} 사용자가 생일을 미래 날짜로 입력함
 */
function parse(birthday: string): Date {
  let date = new Date(birthday)
  if (!isValid(date)) {
    throw new InvalidDateFormatError('Enter a date in the form YYYY/MM/DD')
  }
  if (date.getTime() > Date.now()) {
    throw new DateIsInTheFutureError('Are you a timelord?')
  }
  return date
}

try {
  let date parse(ask())
  console.info('Date is', date.toISOString())
} catch (e) {
  if (e instanceof InvalidDateFormatError) {
    console.error(e.message)
  } else if (e instanceof DateIsInTheFutureError) {
    console.info(e.message) 
  } else {
    throw e
  }
}

예외 반환

: 유니온 타입을 이용해 자바의 throw문을 비슷하게 흉내냄

...
function parse(birthday: string): Date | InvalidDateFormatter | DateIsInTheFutureError {
  let date = new Date(birthday)
  if (!isValid(date)) {
    return new InvalidDateFormatError('Enter a date in the form YYYY/MM/DD')
  }
  if (date.getTime() > Date.now()) {
    return new DateIsInTheFutureError('Are you a timelord?')
  }
  return date
}
// => 메서드를 사용할 때 세 가지의 상황을 처리해야지 않으면 컴파일 타임에 TypeError 발생

...
let result = parse(ask())		// 날짜 또는 에러
if (result instanceof InvalidDateFormatError) {
  console.error(result.message)
} else if (result instanceof DateInTheFutureError) {
  console.info(result.message) 
} else {
  console.info('Date is', date.toISOString())
}

// 에러를 개별적으로 처리하지 않고 한번에 처리
let result = parse(ask())		// 날짜 또는 에러
if (result instanceof Error) {
  console.info(result.message) 
} else {
  console.info('Date is', date.toISOString())
}

Option 타입

: 특수 목적 데이터 타입(NPM 설치 필요 or 직접 구현해야 함)
-- ex) Try, Option, Either 타입
: 어떤 특정 값을 반환하는 대신 값을 포함하거나 포함하지 않을 수도 있는 컨테이너를 반환
-- 컨테이너는 자체적으로 몇 가지 메서드를 제공

  • 장점
    : 성공하거나 실패할 수 있는 연산을 연달아 수행할 때 유용하게 사용 가능
    : 오버로드 기능을 추가하면 Some과 None으로만 제한하여 코드를 훨씬 아전하게 만들 수 있음

  • 단점
    : 이런 데이터 타입을 사용하지 않는 다른 코드와는 호환되지 않음
    : None으로 실패를 표현하기 때문에 실패한 이유를 자세히 알려주지 못함

Option 타입 정의
  1. Option은 Some< T >와 None이 구현하게 될 인터페이스
    Some< T >: T라는 값을 포함하는 Option
    None: 실패한 상황의 Option
  2. Option은 타입이기도 하고 함수이기도 함
    타입 관점: 단순히 Some과 None의 슈퍼타입
    함수 관점: Option 타입의 새 값을 만드는 기능
// step1) 기본 정의
interface Option<T> { }
class Some<T> implements Option<T> {
  constructor(private value: T) { }
}
class None implements Option<never> { }

// step2) 연산 정의
interface Option<T> {
  // 비어있을 수도 있는 Option에 연산을 연쇄적으로 수행하는 수단
  flatMap<U>(f: (value: T) => Option<U>): Option<U>
  // Option에서 값을 가져옴
  getOrElse(value: T): T
}

// step3) 메서드 구현
class Some<T> implements Option<T> {
  constructor(private value: T) { }
  flatMap<U>(f: (value: T) => Option<U>): Option<U> {
    return f(this.value)
  }
  getOrElse(): T {
    return this.value
  }
}

class None implements Option<never> { 
  flatMap<U>(): Option<U> {
    return this
  }
  getOrElse<U>(value: U): U {
    return value
  }
}

// step4) 시그니처 오버로드
interface Option<T> {
  flatMap<U>(f: (value: T) => None): None
  flatMap<U>(f: (value: T) => Option<U>: Option<U>
  getOrElse(value: T): T
}

class Some<T> implements Option<T> {
  constructor(private value: T) { }
  flatMap<U>(f: (value: T) => None): None
  flatMap<U>(f: (value: T) => Some<U>): Some<U>
  flatMap<U>(f: (value: T) => Option<U>): Option<U> {
    return f(this.value)
  }
  getOrElse(): T {
    return this.value
  }
}

class None implements Option<never> {
  flatMap<U>(): None {
    return this
  }
  getOrElse<U>(value: U): U {
    return value
  }
}

// step 5) 함수 구현 + 시그니처 오버로드
function Option<T>(value: null | undefined): None
function Option<T>(value: T): Some<T>
function Option<T>(value: T): Option<T> {
  if(value == null) {
    return new None
  }
  return new Some(value)
}

// 사용 예시
let result = Option(6)				// Some<number>
	.flatMap(n => Option(n * 3))	// Some<number>
	.flatMap(n => new None)			// None
	.getOrElse(7)					// 7
profile
프론트엔드 개발자

0개의 댓글