자바스크립트는 뭐든 허용한다. 숫자를 넣을 자리에 문자열을 넣어도, 있지도 않은 속성을 읽어도 군말 없이 받아준다. 그 관대함이 편할 때도 있지만 대가는 대개 새벽에 치른다. 잘못된 값이 어디서 에러도 없이 조용히 흘러다니다가, 하필 사용자 화면에서 터지기 때문이다. 그러면 로그를 거꾸로 뒤지며 이 undefined가 대체 어디서 태어났는지 추적하는 밤이 시작된다.


타입스크립트(TS, 자바스크립트에 타입을 얹은 언어)가 하는 일은 딱 하나다. 코드를 돌리기 전에, 그러니까 컴파일 타임(소스 코드를 검사하고 변환하는 시점)에 값이 엉뚱한 자리에 들어가는 걸 미리 잡아준다. 타입(값의 종류를 못박는 이름표)이란 그 검사의 기준이다. 문법을 외우기 전에, 이게 실제로 어떤 사고를 막아주는지부터 보는 게 순서다. 아래 결과는 전부 실제 타입 검사기를 돌려 나온 진짜 에러 메시지를 그대로 옮긴 것이다.


자바스크립트는 틀린 걸 틀렸다고 안 한다. 가격과 수량을 더하는 함수가 있다 치자. 어디선가 가격이 숫자가 아니라 문자열 "100"으로 넘어온다. 서버 응답이나 입력창 값은 십중팔구 문자열이라 흔한 상황이다.


function total(price, qty) {
return price + qty;
}
console.log(total("100", 5)); // "1005"


105가 나와야 할 자리에 "1005"가 찍힌다. 문자열이 +를 만나면 자바스크립트는 덧셈 대신 이어붙이기를 하기 때문이다. 그런데 에러는 한 줄도 안 뜬다. 이 값이 그대로 결제 금액에 박혀 배포된 뒤에야, 고객센터를 통해 문제를 전해 듣게 된다. 진짜 무서운 버그는 시끄럽게 죽는 버그가 아니라, 이렇게 조용히 틀린 값을 내놓는 버그다.


타입스크립트는 돌리기도 전에 빨간 줄을 긋는다. 같은 함수에 매개변수 타입만 붙여보자. 이름 뒤에 콜론(:)을 찍고 타입을 적는 게 전부다.


function total(price: number, qty: number) {
return price + qty;
}
total("100", 5);


이제 편집기에서 "100" 밑에 빨간 물결 줄이 그어지고 이 메시지가 뜬다.


error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.


'string' 타입 인자를 'number' 타입 매개변수에 넣을 수 없다는 뜻이다. 코드를 실행하기도 전에, 저장하는 순간 잡힌다. 위의 "1005" 사고가 배포는커녕 내 화면을 벗어날 수조차 없게 된다. 고쳐야 할 곳도 정확히 그 줄 하나로 찍어준다.


오타 난 속성 이름도 잡는다. 실무에서 제일 흔한 삽질이 속성 이름 오타다. 자바스크립트는 없는 속성을 읽으면 그냥 undefined를 돌려주고 넘어간다.


const user = { name: "hong", age: 20 };
console.log(user.nam); // undefined


namenam으로 잘못 쳤는데도 자바스크립트는 아무 불평 없이 undefined를 준다. 이 undefined가 화면 어딘가에 "undefined님 환영합니다"로 나타나서야 오타를 눈치챈다. 타입스크립트는 다르다.


error TS2551: Property 'nam' does not exist on type '{ name: string; age: number; }'. Did you mean 'name'?


'nam' 속성은 없다고 못박고, 'name'을 쓰려던 것 아니냐고 되묻기까지 한다. user에 타입을 붙인 적도 없는데 이걸 안다. 객체 생김새를 보고 알아서 타입을 정하기(추론) 때문이다.


덤으로 자동완성이 딸려온다. 타입을 잡은 이유가 에러 검출뿐이라고 생각하면 손해다. 편집기가 그 타입 정보를 읽어서, user.까지만 쳐도 넣을 수 있는 속성 목록을 띄워준다.


user. // 편집기가 name, age 를 자동으로 띄워줌


속성 이름을 외우거나 다른 파일을 열어 확인할 필요가 없어진다. 타입은 곧 그 값의 사용 설명서라, 주석과 달리 낡지도 않고 항상 코드와 일치한다. 오타 방지, 자동완성, 살아있는 문서가 표기 한 줄에서 한꺼번에 나온다.


변수에 엉뚱한 타입을 넣는 것도 막는다. 여기서 중요한 건 타입을 일일이 안 적어도 된다는 점이다. let n = 5라고만 써도 타입스크립트는 이 변수가 숫자를 담는 자리라고 스스로 판단한다.


let n = 5; // n은 number로 추론됨
n = "hello";


error TS2322: Type 'string' is not assignable to type 'number'.


처음 넣은 5만 보고 이 자리는 숫자용이라 정해놓고, 뒤에 문자열이 오자 거부한다. 타입 표기를 한 글자도 안 했는데 말이다. 그래서 타입스크립트를 쓴다고 코드가 콜론 범벅이 되진 않는다. 애매한 곳에만 표기하고 나머진 추론에 맡기는 게 실무의 기본이다.


인자 개수까지 센다. 함수가 받기로 한 값을 빠뜨리는 것도 잡아준다.


function greet(name: string) {
return "hi " + name;
}
greet();


error TS2554: Expected 1 arguments, but got 0.


1개를 받아야 하는데 0개가 왔다는 것이다. 자바스크립트라면 빈 자리에 undefined가 슬쩍 들어가 "hi undefined"가 찍혔을 텐데, 그 사고를 함수 입구에서 막는다.


결국 자바스크립트로 돌아간다. 오해하기 쉬운데, 브라우저는 타입스크립트를 못 읽는다. tsc(타입스크립트 컴파일러)가 타입을 다 검사한 뒤 이름표를 전부 떼어내고 평범한 자바스크립트로 바꿔서 내보낸다. 그래서 타입은 프로그램이 돌아가는 동안엔 아무 흔적도 남기지 않는다. 실행 속도가 느려지지도, 결과물이 무거워지지도 않는다. 순전히 개발할 때만 쓰는 안전 장비인 셈이다. 검사만 해보고 싶으면 이 한 줄이면 된다.


npx tsc --noEmit


--noEmit은 결과 파일은 만들지 말고(no emit) 타입 검사만 하라는 뜻이다. 문제가 있으면 위에서 본 메시지들이 쏟아지고, 깨끗하면 조용히 통과한다. 편집기의 빨간 줄과 똑같은 검사를 명령줄에서 한 번에 돌리는 것이라, 배포 전 마지막 관문으로 걸어두기 좋다.


타입을 붙이는 건 처음엔 손이 더 간다. 대신 방금 본 사고들이 사용자에게 닿기 전에, 아직 내 화면에서 빨간 줄로 걸린다. 프로그램이 커지고 여럿이 같이 짤수록 이 안전망 값어치가 뛴다. 함수 하나를 고쳤을 때 그 여파로 어디가 깨지는지, 사람이 일일이 확인하는 대신 컴파일러가 전부 짚어주기 때문이다. 혼자 짠 짧은 스크립트라면 타입 없이도 머릿속에 다 들어오지만, 코드가 수천 줄이 되고 몇 달 전에 짠 함수를 다시 건드릴 때가 오면 이 이름표들이 기억을 대신해준다. 어떤 값이 오가는지 코드에 박혀 있으니, 짐작으로 고치다 엉뚱한 데를 터뜨리는 일이 준다.