타입스크립트란

Jin·2022년 3월 1일
0

Typescript

목록 보기
1/4

타입스크립트 (이하 TS)는 자바스크립트 (이하 JS)의 불완전성을 보완해주는 언어입니다.

TS는 흔히 발생하는 실수를 방지하며, 문서화를 제공하고, 리팩터링을 쉽게 만들며, 단위 테스트의 숫자를 반으로 줄임으로 더 안전한 프로그램을 구현할 수 있게 보장해줍니다.

여기서, '안전한'이란 타입의 안정성을 뜻합니다.

타입 안정성은 타입을 이용해 프로그램이 유효하지 않은 작업을 수행하지 않도록 방지합니다.

  • 숫자와 리스트의 연산
  • 객체에 존재하지 않는 멤버 함수 호출
  • 다른 곳으로 이동된 모듈을 임포트하기

위의 사례들이 그 예입니다. JS는 이런 실수가 있는 코드라도 최대한 실행하려 시도합니다. 유효하지 않은 작업이라도 개발자가 실제로 의도한 것이 있을 거라고 짐작하기 때문입니다.

3 + []; // "3"

let obj = {};
obj.foo; // undefined

function a(b) {
  return b / 2;
}

a("z"); // NaN

위의 연산들은 명백하게 잘못된 동작임에도 JS는 예외를 던지지 않고 최선을 다해 결과를 도출합니다. JS의 이런 기능은 물론 도움이 됩니다. 하지만, 버그를 쉽게 확인하는 데에는 도움이 되지 않습니다.

우리의 실수를 바로잡으려는 시도는 프로그래밍 언어가 갖춰야 할 훌륭한 기능입니다. 하지만 JS에서는 이 기능 때문에 코드에 실수를 저지른 시점 (코딩 시점)과 그 실수를 인지하는 시점 (런타임 시점)이 달라지기 쉽습니다.

그래서 TS가 등장하였습니다!!

TS가 에러를 알려준다는 사실 자체보다 더 훌륭한 기능은 바로 에러를 알려주는 시점입니다. 개발자가 텍스트 편집기에 코드를 입력하는 순간 곧바로 에러 메시지를 발생시킵니다.

3 + []; // error

let obj = {};
obj.foo; // error

function a(b) {
  return b / 2;
}

a("z"); // error

이런 식으로 곧바로 에러를 발생시키므로 우리는 이 에러를 수정해야지만 컴파일 후 프로그램을 실행해볼 수 있게 됩니다.


컴파일러

프로그램은 전체 개요는 이렇습니다.

프로그램은 프로그래머가 작성한 다수의 텍스트 파일로 구성됩니다. 이 텍스트를 컴파일러라는 특별한 프로그램이 파싱하여 추상 문법 트리 (abstract syntax tree, AST)라는 자료구조로 변환합니다. AST는 공백, 주석, 탭이냐 공백이냐 등의 결과를 완전히 무시합니다. 그리고 컴파일러는 다시 AST를 바이트코드라는 하위 수준의 표현으로 변환합니다. 바이트코드가 완성되면 런타임이라는 다른 프로그램에 바이트코드를 입력해 평가하고 결과를 얻을 수 있습니다.

즉, 프로그램을 실행한다는 것은 컴파일러가 소스 코드를 파싱해 AST로 만들고, 다시 AST를 바이트코드로 변환한 것을 런타임이 평가하도록 지시한다는 의미입니다. 이 과정은 다음과 같이 요약할 수 있습니다.

  • 프로그램이 AST로 파싱된다.
  • AST가 바이트코드로 컴파일된다.
  • 런타임이 바이트코드를 평가한다.

여기서, 다른 여느 언어들과 TS의 차이점이 드러납니다. TS는 컴파일러가 코드를 바이트코드 대신 JS 코드로 변환합니다!

그리고 TS 컴파일러는 AST를 만들어 결과 코드를 내놓기 전에 타입 확인을 거칩니다. 여기서 이미 타입 관련 버그는 다 발생하게 되는 것입니다.

이후로는 JS 프로그램이 실행하듯이 이 과정을 JS 코드가 거치게 됩니다.

결국, TS 컴파일 및 실행은 이런 과정을 거칩니다.

  1. TS 소스 -> TS AST
  2. 타입 검사기가 AST를 확인
  3. TS AST -> JS 소스
  4. JS 소스 -> JS AST
  5. JS AST -> 바이트코드
  6. 런타임이 바이트코드 실행 및 평가

1~3은 TSC가 수행하며, 4~6은 JS 런타임이 실행합니다.

이러한 과정에서 볼 수 있는 것은 개발자가 코드에 기입한 타입 정보는 최종적으로 만들어지는 프로그램에 아무런 영향을 주지 않으며, 단지 타입을 확인하는 데에만 쓰인다는 사실입니다. 따라서 마음대로 프로그램의 타입을 바꾸고 개선 혹은 실험하여도 기존의 응용 프로그램이 망가질 염려는 없습니다.


타입 시스템

최신 언어는 저마다의 타입 시스템을 갖추고 있습니다. TS도 예외는 아닙니다.

타입 시스템은 보통 두 가지 종류, 즉 어떤 타입을 사용하는지를 컴파일러에 명시적으로 알려주는 타입 시스템과 자동으로 타입을 추론하는 타입 시스템으로 구분됩니다.

TS는 두 가지 시스템 모두의 영향을 받았습니다. 즉, 개발자는 타입을 명시하거나 TS가 추론하도록 하는 방식 중에서 선택할 수 있습니다.

어노테이션을 이용하면 TS에 명시적으로 타입을 지정할 수 있습니다.

let a: number = 1; // a는 number
let b: string = 'hello'; // b는 string
let c: boolean[] = [true, false]; // c는 boolean 배열

위의 코드에서 굳이 number, string, boolean[]이라는 어노테이션을 사용하지 않아도 TS가 알아서 타입을 추론합니다.

저는 TS 규칙은 꼭 필요할 때에만 어노테이션을 사용하며 그렇지 않으면 TS가 타입을 추론하도록 하는 것입니다. 그 이유는 성능상 거의 차이가 없으며 TS가 타입을 추론하도록 두는 것이 코드를 줄일 수 있는 방법이기 때문입니다.

JS와 TS의 주요 차이점을 정리하면 다음과 같습니다.

타입 시스템 기능자바스크립트타입스크립트
타입 결정 방식동적정적
타입이 자동으로 변환되는가?OX (대부분의 경우)
언제 타입을 확인하는가?런타임컴파일 타임
언제 에러를 검출하는가?런타임 (대부분)컴파일 타임 (대부분)

동적 타입 바인딩이라 함은 JS가 프로그램을 실행해야만 특정 데이터의 타입을 알 수 있음을 의미합니다. JS는 프로그램을 실행하기 전에는 타입을 알 수 없습니다.

TS는 점진적으로 타입을 확인하는 언어입니다. 즉, TS는 컴파일 타임에 프로그램의 모든 타입을 알고 있을 때 최상의 결과를 보여줄 수 있지만, 프로그램을 컴파일하는 데 반드시 모든 타입을 알아야 하는 것은 아닙니다. 하지만, 모든 타입을 알고 있지 못하면 런타임 시점에서 오류가 사용자에게 그대로 노출될 위험성도 그만큼 높아진다는 것이므로 바람직하지는 않습니다.

저는 TS 사용 규칙에서 모든 코드의 타입을 컴파일 타임에 지정하는 것을 목표로 삼고 있습니다. 그것이 BEST CASE이기 때문입니다.

JS는 약한 타입의 언어입니다.

3 + [1]; // "31"

누가 봐도 말이 안되는 연산이지만 JS는 이것을 "31"로 변환하며 에러도 없습니다. JS가 제공하는 이런 종류의 암묵적 변환 때문에 문제의 원인을 추적하기가 어려워지고, 이는 JS 프로그래머의 고질적인 골칫거리였습니다. 여기서 얻을 수 있는 포인트는 타입을 변환할 때는 절대로 암묵적으로 JS 엔진이 추론하도록 두지 않고 명시적으로 해야 한다는 것입니다. 그래야만 디버깅 추적, 협업, 코드 확장 등에서 원활하게 작업을 수행할 수 있습니다.

TS는 정적으로 코드를 분석해 이런 에러를 검출하여 코드를 실행하기도 전에 알려줍니다. 코드가 컴파일되지 않는다는 사실은 개발자가 실수를 저질렀음을 뜻하므로 코드를 실행하기 전에 실수를 바로잡을 수 있는 좋은 기회가 됩니다. 반면, JS는 런타임에 예외를 던지거나 암묵적으로 형변환을 수행합니다. 즉, 프로그램을 실행해야만 어떤 문제가 있음을 확인할 수 있습니다.

TS는 컴파일 타임에 문법 에러와 타입 관련 에러를 모두 검출합니다. 순수 JS 세계에서 런타임 에러로 발생했을 많은 에러들을 TS는 컴파일 타임에서 확인할 수 있다는 것이 TS가 가지는 핵심입니다.

profile
배워서 공유하기

0개의 댓글