Typescript Unsoundness

Seal Park·2022년 10월 2일
0

시작에 앞서,

개인적으로, 자바스크립트로 코딩을 하다보면 런타임에 예측할 수 없는 에러를 발생시킬 때가 있었어요. 코드 작성 단계에서 실수로 지나칠 수도 있고, 프로젝트가 조금이라도 커져서 관리가 조금 어려워지면 지나칠 수 있는 일들도 뜨문뜨문 발생하는 것 같아요. 정확히 타입스크립트의 등장 배경은 어떤지는 모르겠지만, 아무래도 자바스크립트의 동적 타입 및 기타 사건들로 인해 발생하는 런타임 에러를 사전에 방지하기 위해 만들어진게 이유 중 하나이지 않을까? 하는 생각입니다.

타입스크립트가 아무리 코드 작성 단계에서 타입 체커에 의해 오류를 줄여준다 해도, 런타임에 발생하는 오류들이 생기기 마련입니다. 이러한 이유가 타입스크립트의 타입 시스템이 불건전(unsoundness)한 경우도 있기 때문입니다. 하지만 이는 타입스크립트 팀에서 의도적으로 고려한 부분이며, 그렇게 고려한 부분에 대해 설명하는 문서가 있습니다. 아래는 불안전성에 대한 짧은 인용문입니다..!

The places where TypeScript allows unsound behavior were carefully considered, and throughout this document we’ll explain where these happen and the motivating scenarios behind them.

타입스크립트의 불안전성으로 인해 의도치 않은 에러를 발생시키는 예제와 함께 어떻게 더 나은 방법으로 코드를 개선할 수 있을지에 대해 포스팅을 시작해보도록 하겠습니다!

간단한 예제로 살펴보는 타입스크립트의 불건전성

any타입

function alertNumber(x: number) {
  alert(x.toFixed(1));  // static type of x is number, runtime type is string
}
const num: any = 'forty two';
alertNumber(num);
// no error, throws at runtime:
// Cannot read property 'toFixed' of undefined

변수 num의 값은 문자열 이지만, 타입은 any입니다. any타입은 타입검사를 하지 않기 때문에 alertNumber의 매개 변수로 num을 설정해도 에러가 발생하지 않으며, 그대로 타입 체커는 타입이 anynum을 검사하지 않습니다. 이는 런타임에서 오류를 발생시키게 됩니다.

해결책은 간단합니다. any타입을 사용하지 않는 것이며, 가능한 any타입 대신 unknown 타입을 사용하는 것이 더 안전한 대안이 될 수 있습니다.

type assertion(타입 단언)

function alertNumber(x: number) {
  alert(x.toFixed(1));
}
const x1 = Math.random() || null;  // type is number | null
alertNumber(x1);
//          ~~ ... Type 'null' is not assignable to type 'number'.
alertNumber(x1 as number);  // type checks, but might blow up at runtime

any 타입보다는 덜 공격적인 타입 단언은 할당된 변수의 타입을 무시하고 개발자가 선언한 타입으로 선언됩니다. 이는 런타임에서 오류를 발생시키게 됩니다.

보통 api response의 타입을 설정할 때 타입 단언을 사용하며, 실제 api response와 타입이 일치하지 않더라도 에러는 발생하지 않습니다.

이는 분기 처리 및 타입 가드를 통해 타입 단언을 피할 수 있습니다.

Array 및 Object 조회

const xs = [0, 1, 2];  // type is number[]
const x = xs[3];  // type is number

console.log(x.toFixed(1);
// no error, throws at runtime:
// Cannot read property 'toFixed' of undefined

다음 코드를 보면 xs는 길이가 3인 숫자 배열 입니다. 변수 x는 3번 인덱스의 값을 할당받았고, x의 타입은 undefined가 아닌 number라는 사실입니다! 코드 작성단계에서는 에러 하이라이팅이 뜨지 않지만, 런타임에서는 오류를 발생시킵니다.

또 하나의 예제를 볼까요?

type IdToName = { [id: string]: string };
const ids: IdToName = {'007': 'James Bond'};
const agent = ids['008'];  // 정적 타입에서는 문자열이지만, 런타임에서는 undefined입니다.

마찬가지로 agentundefined가 할당되어야 하지만, 코드 작성 단계에서는 string 타입이며, 또한 에러 하이라이팅이 뜨지 않습니다. 이는 런타임 단계에서 undefined 타입이며, agentString 메서드를 사용하면, 런타임 에러를 발생시킵니다.

이러한 유형의 에러를 발생시키지 않는 방법은 애초에 이런 유형의 코드를 작성하지 않는 게 제일 중요하며, 중간에 타입가드를 통해 코드를 보다 안전하게 작성해야 합니다.

마치며

한 블로그에서 타입스크립트는 통계 다트판을 예시로 타입스크립트의 건전성은 정확함 보다는 얼마나 정밀한지에 대해 이야기를 합니다. 이어서 주장하길, 타입스크립트는 sound(건전)하지 않으며, 타입스크립트는 건전성에 대한 목표보다는 타입스크립트의 편리성과 자바스크립트 라이브러리와 잘 어우러지는지에 대해 집중하고 있다고 설명합니다.

위의 몇 가지 불건전(unsoundness)한 코드 예시를 통해 런타임 환경에서 발생하는 에러에 대해 알아 보았는데요, 어쩔 수 없이 위의 예제처럼 작성하는 경우가 있지만, 최대한 피하는 것이 좋고, 피할 수 없다면 런타임에서도 검증이 가능한 분기 설정 및 타입 가드를 설정하는 것을 권장하고 있습니다.

References

https://www.typescriptlang.org/docs/handbook/type-compatibility.html

https://effectivetypescript.com/2021/05/06/unsoundness/

0개의 댓글

관련 채용 정보