7장 에러 처리

2021. 07. 24

1. return null

function ask() {
  return prompt('When is your birthday?');
}

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

function parse(birthday: string): Date {
  const date = new Date(birthday)
	if (isValidDate(date) {
    return date;
	}
  return null;
}

const date = parse(ask());
if (date) {
	console.log('Date is', date.toISOString());
} else {
	consoel.error('Error parsing date for some reason');
}
  • Pros

    • 타입 안정성을 유지하면서 에러를 처리하기 가장 간단함
  • Cons

    • 문제가 생긴 원인을 알 수 없다.
    • return type이 null이므로 타입 조합이 어려워진다.
    • 모든 연산에서 Null을 체크해야 한다. 이로인해 연산이 중첩되거나 연결 코드가 지저분해 진다.

2. throw exception

function parse(birthDay: string): Date {
  const date = new Date(birthDay);
  if (isValidDate(date) {
    return date
  }
  throw new RangeError('Enter a date in the form YYYY/MM/DD');
}

try {
  const 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 {}

function parse(birthDay: string): Date {
  const date = new Date(birthDay);
  if (!isValidDate(date) {
    throw new InvalidDateFormatError('Enter a date in the form YYYY/MM/DD');
  }
  if (date.getTime() > Date.now()) {
    throw new DateIsInTheFutureError('Invalid date range');
  }
	return date
}

try {
  const date = parse(ask());
  console.info('Date is', date.toISOString());
} catch (e) {
  if (e instanceof InvalidDateFormatError) {
    console.error(e.message);
  } else if (e instance of DateIsInTheFutureError) {
    console.warn(e.message);
  } else {
    throw e
  }
}
  • throw exception을 사용해서 문제에 대한 대처 및 디버깅에 도움이 되는 메타데이터를 얻을 수 있다.
  • 처리하지 않은 에러는 e 는 다시 throw 한다.
  • 에러를 서브클래싱하여 더 구체적으로 표현하여 에러를 구분할 수 있다.

3. return exception

function ask() {
  const text = prompt("When is your birthday?")
  return text || ""
}

function isValidDate(date: Date) {
  return (
    Object.prototype.toString.call(date) === "[object Date]" &&
    !Number.isNaN(date.getTime())
  )
}

const date = parse(ask())
if (date) {
  console.log("Date is", date.toISOString())
} else {
  console.error("Error parsing date for some reason")
}

class InvalidDateFormatError extends RangeError {}
class DateIsInTheFutureError extends RangeError {}

function parse(
  birthDay: string
): Date | InvalidDateFormatError | DateIsInTheFutureError {
  const date = new Date(birthDay)
  if (!isValidDate(date)) {
    throw new InvalidDateFormatError("Enter a date in the form YYYY/MM/DD")
  }
  if (date.getTime() > Date.now()) {
    throw new DateIsInTheFutureError("Invalid date range")
  }
  return date
}

const result = parse(ask())

if (result instanceof InvalidDateFormatError) {
  console.error(result.message)
} else if (result instanceof DateIsInTheFutureError) {
  console.warn(result.message)
} else {
  console.info(result.toISOString())
}

4. Option

함수형 프로그래밍 간단히 맛보기

class Box {
  constructor(value) {
    this.$value = value
  }

  static of(value) {
    return new Box(value)
  }

  map(fn) {
    return new Box(fn(this.$value))
  }
}

const addFive = num => {
  return num + 5
}

// 첫 글자를 대문자로 바꿔줍니다.
const startCase = str => {
  return str.charAt(0).toUpperCase() + str.slice(1)
}

Box.of(1).map(addFive) // Box(6)
Box.of(1)
  .map(addFive)
  .map(addFive) // Box(11)
Box.of("hello, FP").map(startCase) // Box('Hello, FP')
class Maybe {
  constructor(value) {
    this.$value = value
  }

  static of(value) {
    return new Maybe(value)
  }

  get isNothing() {
    return this.$value === null || this.$value === undefined
  }

  map(fn) {
    return this.isNothing ? this : Maybe.of(fn(this.$value))
  }

  toString() {
    return this.isNothing ? "Nothing" : `Just(${this.$value})`
  }
}

const books = [
  { id: "book1", title: "coding with javascript" },
  { id: "book2", title: "speaking javaScript" },
]

const getById = id => arr => arr.filter(v => v.id === id)

const getBookById = (id, books) => Maybe.of(books).map(getById(id))

getBookById("book1", books)
getBookById("book2", books)
getBookById("book3", books)
class Either {
  constructor(value) {
    this.$value = value
  }

  static right(value) {
    return new Right(value)
  }

  static left(value) {
    return new Left(value)
  }
}

class Right extends Either {
  get isRight() {
    return true
  }

  get isLeft() {
    return false
  }

  map(fn) {
    return new Right(fn(this.$value))
  }
}

class Left extends Either {
  get isRight() {
    return false
  }

  get isLeft() {
    return true
  }

  map(fn) {
    return this
  }
}

특수 목적 데이터 타입을 사용해 예외를 표현하는 방법도 존재한다.

  • Pros

    • 에러가 발생할 수 있는 계산에 여러 연산을 연쇄적으로 수행할 수 있게 된다.
  • Cons

    • 특수 목적 데이터 타입을 사용하지 않는 다른 코드와 호환이 되지 않음
// ...
function parse(birthday: string): Date[] {
  const date = new Date(birthday)
  if (!isValid(date)) return []
  return [date]
}

const date = parse(ask())
date.map(toISOString).forEach(_ => console.info("Date is"))

⇒ if, prompt always success, parse always fail

function ask() {
  const res = prompt("When is your birthday?")
  if (!res) return []
  return [result]
}

ask()
  .map(parse)
  .map(date => date.toISOString()) // error
  .forEach(date => console.info("Date is", date))

To solve

Container 라는 특수한 데이터 타입에 담아서 상황을 해결하자

이를 해결하기 위한 방법으로, Option을 사용

  • Option

    • SomeNone 이 구현하게 될 인터페이스
    • Some, None <: Option
    • Option은 타입이기도 하고 함수이기도 하다.
interface Option<T> {}

class Some<T> implements Option<T> {
  constructor(private value: T) {}
}
// 결과 값을 표현하기 위한 타입 및 값

class None implements Option<never> {} // 연산 실패를 나타내는 값

Option = [T] | []

Some = [T]

None = [];

interface Option<T> {
  flatMap<U>(f: (value: T) => None): None
  flatMap<U>(f: (value: T) => Option<U>): Option<U>
  getOrElse(value: T): T
}

function Option<T>(value: null | undefined): None
function Option<T>(value: T): Some<T>
function Option<T>(value: T): Option<T> {
  if (value === null || typeof value === "undefined") return new None()
  return new Some(value)
}

class Some<T> extends 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 extends Option<never> {
  flatMap<U>(): None {
    return this
  }
  getOrElse(value: U): U {
    return value
  }
} // 연산 실패를 나타내는 값

© 2024 Doe의 devlog, Built with Vapor blog Theme Gatsby