타입스크립트는 런타임에 발생할 수 있는 예외를 컴파일 타임에도 잡을 수 있도록 최선을 다함
사용자의 생일을 입력 받아 Date 객체로 파싱하는 프로그램을 구현해보자
function ask() {
return prompt ('When is your birthday?')
}
function parse(birthday: string) :Date {
return new Date(birthday)
}
let date = parse(ask())
console.info('Date is', date.toISOString())
단순한 텍스트 입력창을 사용 했으므로 사용자가 입력한 내용을 검증해야함
입력한 내용을 사용하기 전에 가장 먼저 결과가 null인지 확인
가장 간단히 null을 반환하여 타입시스템이 코드가 두 가지 상황을 모두 처리하는지를 확인
하지만 parse에서 발생하는 에러를 이 방식으로 처리하면 문제가 생긴 원인을 알 수 없기에 개발자 로그를 뒤져도 '알 수 없는 오류가 발생했습니다.' 같은 모호한 에러 메세지만을 보게 될 것
또한, 모든 연산에서 null을 확인해야 하므로 연산을 중첩하거나 연결할 때 코드가 지저분해짐
문제가 발생하면 null 대신 예외 던지기
에러를 catch로 잡는 것보다 처리하지 않은 에러는 다시 던지는 것이 좋음
또한, 나중에 다른 개발자가 parse나 ask에서 또 다른 형태의 RangeError를 던질 수 있게 하려면 이렇게 아래처럼 커스텀 에러를 사용해서 구체적으로 표현하면 됨
여기서 다른 개발자가 특정 타입의 에러와 기존 RangeError가 던져질 수 있다는 사실을 어떻게 알고 잡아 처리할 수 있는 방법은 함수 이름에 명시하거나 문서화 주석에 정보를 추가하면 됨
근데 코드를 사용하는 개발자에게 성공과 에러 상황 모두를 처리하도록 알려주려면 어떻게 해야 할까?
타입스크립트는 자바가 아니기에 throws문을 지원하지 않음 하지만 유니온 타입을 활용하여 비슷하게 흉내낼 수 있음
이제 이 메서드는 세가지 상황을 처리해야 하고 그렇지 않으면 컴파일 타임에 TypeError가 발생함
이상으로 TS의 타입 시스템을 활용하여 다음을 수행함
다음처럼 명시적으로 처리도 가능
한편 에러를 던지는 연산을 연쇄적으로 호출하거나 중첩하면 코드가 지저분해진다는 단점이 있음, T | Error1을 반환하는 함수를 이용하는 모든 호출자 함수는 두 가지 선택지 중 하나를 고를 수 있음
특수 목적 데이터 타입을 사용해 예외를 표현
이 방식은 값과 에러의 유니온을 반환하는 방법에 비해 단점이 있지만 에러가 발생할 수 있는 계산에 여러 연산을 연쇄적으로 수행할 수 있음
어떤 특정 값을 반환하는 대신 값을 포함하거나 포함하지 않을 수도 있는 컨테이너를 반환한다는 것이 Option 타입의 핵심
컨테이너는 자체적으로 몇 가지 메서드를 제공
값을 포함할 수 있다면 어떤 자료구조로도 컨테이너를 구현할 수 있음
다음은 배열로 구현한 모습
Option도 에러가 발생한 이유를 알려준다기 보다 그냥 좀 잘못됐음을 알려만 줌
언제든 실패할 수 있는 여러 동작을 연쇄적으로 수행할 때 Option의 진가가 발휘됨
prompt는 항상 성공하고 parse는 실패할 수 있다고 가정
그런데 propmt도 실패할 수 있다면? 이때 또 다른Option을 이용해 처리할 수 있음
하지만 여기서 에러는 Date의 배열 (Date[])를 Date의 배열의 배열 (Date[][])로 매핑했기 때문에 Date의 배열로 평탄화를 해줘야함
이렇게 여러 코드로 나뉜다면 타입이 많은 정보를 제공하지 않고 무슨 일이 일어나는지를 한눈에 볼 수 없기 때문에
우리가 하려는 작업을 컨테이너에 담아서 상황을 개선하면 된다
이때 컨테이너는 대상 값을 이용해 연산을 수행하는 방법과 그 결과를 얻어내는 방법을 드러내는 역할을 함
Option 타입을 다음 처럼 정의
배열 기반 구현과 비교
이 옵션으로 할 수 있는 것
flatMap 비어있을 수도 있는 Option을 연산에 연쇄적으로 수행하는 수단
getOrElse Option에서 값을 가져옴
flatMatp은 T타입의 값을 받는 f를 인수로 받아 U 타입의 값을 포함하는 Option을 반환, flatMap은 Option의 값을 인수로 건네 f를 호출한 다음 새로운 Option<U>를 반환함
getOrElse는 T 타입의 값을 기본적으로 받은 다음, Option이 빈 None이면 기본값을 반환하고 Option이 Some<T>이면 Option 안의 값을 반환
이제 두 클래스에 메서드를 구현해 넣어보자
None의 매핑 결과는 항상 None이며 Some<T>의 매핑 결과는 Some<T>나 None이 된다는 사실을 알 수 있다
이러한 것을 이용하여 우리는 오버로드를 통해 더 구체적인 타입을 줄 수 있다 (결과가 어떻게 나오는지)
이제 Option을 만드는데 사용할 함수만 구현하면 된다
Option을 인터페이스로 작성했기 때문에 함수의 이름은 같아도 됨 (타입스크립트는 타입과 값을 별도의 네임스페이스로 관리하기 때문)
이제 null일 수 있는 값에도 안심하고 연산을 넣을 수 있음
다음은 실사용 예임생일 예를 가져다가 해도 가능
ask() // Option<String>
.flatMap(parse) // Option<Date>
.flatMap(date => new Some(date.toISOSting())) // Option<string>
.flatMap(data => new Some('Date is ' + date)) // Option<string>
.getOrElse('에러임') // String
하지만 Option의 단점은 None을 반환하기 때문에 정확히 어떤 이유로 에러가 발생했는지 알 수 없다는 것이고
한 가지 더로 Option을 사용하지 않는 애들과는 호환되지 않는 것이 단점이다.
그렇지만 Option을 사용할 때 오버로드 기능을 추가하면 Option을 기본 지원하는 언어를 포함한 대부분의 언어로는 표혀날 수 없는 일도 해낼 수 있도 오버로드된 호출 시그니처를 활용하여 Some과 None으로만 제한한 코드를 훨씬 안전하게 만들 수 있다.