[번역] TypeScript at Google

이 포스트는 Evan Martin의 글, TypeScript at Google을 원 저자의 동의하에 번역한 것이다. 의역이 매우 많고 오역이 있을 수 있으니 가급적 원문도 같이 참고하길 바란다.

나는 이제 TypeScript를 2년 넘게 써왔다. 따라서 이에 대해 리뷰하는 글을 하나 둘 정도 써야겠다고 생각했다. 나는 전형적인 면책선언(Disclaimer)와 함께 글을 시작하려고 한다. 나는 그냥 Google에서 일하는 수 만 명 중에 한 명의 엔지니어 일뿐이고, 여기에 적힌 의견에 완전히 동의하지 않는 사람들도 있다.

Google은 웹 어플리케이션을 일찌감치 채택했다. Gmail이 벌써 14년이 되었다고 하면 믿을 수 있겠는가? 그 당시의 JavaScript는 완전히 미쳤었다(madness). Gmail 엔지니어들은 Internet Explorer의 구린 가비지 컬렉션 알고리즘에 대해 매우 걱정이 많았다. 따라서 엔지니어들은 가비지 컬렉션이 중지되는 상황을 피하기 위해서 for 루프 밖으로 문자열(String) 표현을 직접 호이스팅 시킬 필요가 있었다. 나는 최근 그 시절의 설계 문서를 찾았는데, 그 문서에서는 우리가 JavaScript를 “미니파이(Minifying)” 한다고 부르는 작업을 고려하고 있었다. 그러나 그 중 몇몇 후보 도구는 Windows 전용이었다. 요즘에는 상상할 수도 없는 일이다.

그 후 몇 년 간, Google은 큰 JavaScript 앱을 개발하는 데 쓰일 수 많은 기반 기술을 개발했다. 예를 들면, 소스 파일이 상호의존성을 표기하도록 만드는 모듈 시스템이 있다. 소스 파일을 합치고 미니파이해서 브라우저에 호환되는 결과물로 만드는 번들러도 있다. 또 다른 어떤 도구는 동적으로 로드할 수 있는 진입점과 서빙을 위한 공통 부분 요소를 통해 앱의 의존성 그래프를 분석한다. 서버 사이드 렌더링은 일반적이다. 모든 이러한 개념들은 요즘의 웹 개발자들에게는 친숙한 것들이지만, Google의 기술 스택의 발전은 오늘날의 발전과 병렬적이지만 더 앞서서 개발되어왔기 때문에, 결과적으로 개념상으로는 비슷하지만 구체적으로는 달라졌다. 프로세스, 도구, 심지어는 이러한 개념들의 이름까지도 완전히 다르다.

병렬적인 진화의 다른 예는, Google, Facebook, Microsoft가 JavaScript에 정적 체크를 더한 컴파일러를 각각 비슷하지만 서로 호환되지 않게 만들었다는 것이다. 그 중 Google의 컴파일러는 구어체로 Closure라고 한다. (Clojure 언어와 헷갈리지 말자. 더 헷갈리는 건 ClojureScript가 Closure 컴파일러를 쓴다는 것이다.)

Google의 JavaScript 스택은 너무나 뛰어났고, Google이 인터넷의 얼굴을 바꾸어버린 웹 앱을 만들고 유지보수할 수 있게 해주었다. (Google 지도가 릴리즈된 시점이 얼마나 놀라운지 기억하는가? 이제는 위젯 형태의 드래그 가능한 지도를 만드는 것은 너무나 당연하다.) 어떤 부분들은 오늘날의 기술을 능가한다. 예를 들면, Closure 컴파일러는 타입 정보를 사용해서 코드를 최적화하고, 핫 로딩 청크 바운더리를 가로지르는 인라인 함수를 사용하고, 사용하지 않는 코드를 제거해 개별적인 심볼로 만들 수 있는, 여전히 가장 세련된 JavaScript 최적화 도구일지도 모른다.

Google의 JavaScript 스택은 문제 또한 안고있다. Closure가 린터에서부터 출발해 점진적으로 진화해왔다는 사실은 Closure가 정적 타입 문법을 주석에서 사용해야 한다는 것을 의미한다. Closure는 예측하기 어려운 시맨틱을 가졌고 느리며, 버그가 많다. 그리고 코드를 정확하게 작성하지 않으면 맹글링(Mangling)하는 경향이 있다. 오픈 소스임에도 불구하고, 이러한 이유들 때문에 업계 전반적으로 사용되지 않았다. Closure에 익숙한 구글러들을 고용한 회사들을 빼고 말이다. 나는 Google 내에서 JavaScript가 낮은 평가를 받고 있다고 생각하는데, 우리의 까다로운 도구 Closure가 정적 언어의 수다스러움(verbosity)을 동적인 언어의 예측불가능성과 결합했기 때문이라고 생각한다.

그 사이 Google 밖에서도, JavaScript는 진화를 계속했고 점점 더 많은 인기를 얻었다. 앞서 말한 IE 가비지 컬렉션 버그를 우회하기 위해, 우리는 Chrome을 만들었고 Chrome은 v8을, v8은 Node.js를 낳았다. 이는 요즘 대부분의 웹 도구가 그 자체로 JavaScript로 만들어졌다는 걸 의미한다. 과거에 이러한 도구를 만드는데 쓰였었던 Java와는 달리 말이다. 모듈 시스템(UMD, AMD, CommonJS)은 급격히 늘어났다. (ES6가 등장해서 자체 모듈 시스템을 개발했지만, 다른 시스템들과는 어떤 이유로 호환되지 않는다. 에휴.) npm은 도구들과 라이브러리가 공유되는 방식을 통합했다. Webpack은 개발자가 개발하는 동안 실행되고 있는 앱에 동적으로 모듈을 교체할 수 있는 기능을 제공했다.

Google은 이 중 어떤 것도 쓰지 않는다. 숙련된 웹 개발자가 Google에 나타났지만, 그가 다른 시간대에 방문한 것과 같다. Google에는 SASS 같은 CSS 전처리 언어가 있지만 이는 SASS가 아니며 누구도 그것을 좋아하지 않는다. 어떤 팬시한 청크 스플리터도 있지만 이 도구는 써드 파티 JavaScript 라이브러리를 지원하지 않는다. 왜냐하면 이 도구는 JavaScript 라이브러리 생태계가 갖춰지기 전에 만들어졌기 때문이다.

이건 모두 역사에 불과하다. 누군가 Google이 그런 길을 가지 말았어야 했다고 주장할 수 있지만, 그렇게 한다고 우리가 이러한 상황에 있다는 사실을 바꾸지는 않는다. 대신, 흥미로운 질문을 해보자. “우리는 이제 어디로 가야하는가?” 몇 가지의 옵션이 있다. 내 관점은 물론 내 선호에 따라 편향되어 있다.

첫번째 매력적인 옵션은 이 망가진 행성을 버리고 JavaScript를 아예 포함하지 않는 새로운 행성에 정착하는 것이다. 만약 우리가 GWT(Java를 JavaScript로 컴파일 하는 Google의 프로젝트) 혹은 Dart(새로운 언어를 JavaScript로 컴파일 하는 Google의 프로젝트) 혹은 WASM 혹은 당신이 좋아하는 언어 무엇이든, Clojure? Haxe? Elm?.. 등에 더 투자했다면 우리는 JavaScript에 대해 아예 걱정할 필요도 없을 것이다.

PL의 팬으로서 나는 이 옵션을 꽤 좋아한다. 그 언어들의 자격에 대해 신중하게 분석한 자료를 주고 싶지만, 이 포스팅은 충분히 길고, 그에 대한 토론은 별도의 포스팅이 되어야 한다고 생각한다. 그 토론 전에, 몇 가지 고려해야 할 사항이 있다. 만약 다른 언어를 채택하게 된다면, (1) 말 그대로, 우리가 가진 수백만 줄의 코드를 재활용 할 수 없다. “새로운 언어로 처음부터 다시 작성”하는 것은 어떤 상황에서는 적절한 선택이지만, 그게 Gmail 엔지니어들의 시간을 적절히 사용하는 방식인지에 대해서는 논쟁의 여지가 있다. 그리고 (2) 앞서 언급한, 우리가 고용하고자 하는 경험 있는 프론트엔드 프로그래머에게도 거의 도움이 되지 않는다.

모든 것을 새로 쓰는 것을 바꿔 말하면 아무것도 바꾸지 않는 것이다. 오픈된 JavaScript 세계는 아마추어 코드와 left-pad1같은 재앙으로 가득 차 있다고 지적할 수 있다. 좋은 엔지니어는 Google이 프론트엔드를 구축하는 특이한 방식에 적응할 수 있기 때문에, 우리는 언제나 우리의 자체 도구를 개선하고 만들 수 있다. Google이 만들고 있는 앱의 요구사항은 다른 회사들이 만드는 웹 앱들과 성격이 다르다(Google 검색 페이지는 하루에 수십억 회 조회된다)는 점은 우리의 자체 도구가 더 우월하며 실제로 필요하다는 것을 뜻한다. 나는 이러한 관점에도 호의적이다. 여기에는 두 가지 트레이드오프 지점이 있다고 생각한다. 하나는 우리의 자체 도구를 만드는 것이 합리적인 지점이고, 다른 하나는 우리가 자체 도구를 만듦으로서 주류로부터 갈라지도록 만드는 지점이다. 논쟁은 이제 우리가 그 사이 어디에 있느냐는 것이다. 나는 Google이 후자 쪽으로 너무 멀리 왔다고 생각한다. 우리는 C++에 의존하기 때문에 LLVM과 Clang에 기여하여 도움이 될 수 있지만, 우리의 자체 LLVM을 만드는 것으로부터는 많은 부가가치를 얻을 수 없을 것이다.

이러한 사실은 나의 소규모 팀이 추구하고 있던 중간 지점으로 이끌었다. 그것은 점진적으로 적절한 외부 도구를 도입하고 우리의 기존 코드 기반과 연동하는 방법을 찾아내는 것이다. 이 작업은 별로 즐겁지는 않지만, (우리는 그냥 우리의 레거시 코드를 던져버리고 “맞는 방법으로 바로 하지”는 않았다.) 나는 겸허하게 수용하려고 하고, 내적인 요소보다는 외부적인 요소를 보고 싶다.

Google의 JavaScript 갈라파고스 섬으로부터 본토로 돌아가는 여정의 첫 번째는 잘 지원되는 정적 체커를 도입하는 것이었다. 그건 (1) 회사 내에서만 성장한 것이 아니고 (2) 우리의 기존 코드와 유사한 문법을 사용하지만 이미 유명했고 (3) JavaScript로 연결되도록 설계되었으며 (4) 맨 처음 Closure의 동기가 되었던, 대규모의 개발을 위해서 설계 되었다. 그리고 그 도구가 바로 TypeScript다. Closure 컴파일러의 장점은 최적화된 결과물인 반면, TypeScript는 훌륭한 유저 인터페이스를 가지고 있고, 최적화를 전혀 하지 않는다. 이 두 도구는 상호보완적이고, (특정 작업에서는) 같이 사용할 수 있다.

TypeScript는 이미 대부분의 프로젝트에서 잘 사용되고 있었기 때문에 (결국 이것도 채택한 이유 중 하나다.) 우리는 자리를 잡은 언어를 도입함으로서 많은 장점 그러니까, IDE 스타일의 코드 자동완성부터, StackOverflow 답변을 쉽게 찾을 수도 있는 등의 장점을 얻을 수 있다. 우리에게 남은 자작업은 기본적으로 통합이었다. 즉, 앱을 완전히 새로 다시 쓰지 않고 점진적으로 TypeScript로 옮기는 것이었다. 점진적으로 컴파일 하기 위해서 Google 전체 빌드 시스템과의 통합은 신중해야 했다. 이는 대규모 앱에 있어서 치명적일 수 있다. 한 모듈에서 내보내는 API에 영향을 주지 않는 변화는 하위 모듈의 재컴파일을 일으키지 않는다. Closure 타입/모듈 시스템으로의 통합은 ES6 TypeScript 모듈이 대부분의 타입 정보를 보존하면서 Google의 모듈 시스템을 가진 모듈을 임포트 할 수 있다는 걸, 그리고 그 반대도 가능하다는 걸 의미한다. 한 회사는 우리가 만든 도구를 사용하여 그들의 전체 코드 기반을 미니파이된 결과물을 보존하면서도 TypeScript로 자동으로 변환하는데 성공했다.

Google 내에서 TypeScript는 이제 어디에서나 가지각색으로 사용되고 있다. 누군가 Google의 제품을 사용한다면 그 사람은 약간의 TypeScript 코드와 상호작용한다고 말할 수 있다. TypeScript 그 자체로도 정적 타입 프로그래밍과 이제 관성이 붙은 JavaScript 생태계와 잘 균형을 이룬 흥미로운 절충안이다. 이는 우리 엔지니어들이 하는 일이기도 하다. 우리는 서로 다른 관심사 간의 균형을 맞추기 위해 흥미로운 타협을 한다. 나는 우리가 지난 몇 년 간 발견한 몇 가지 흥미로운 점들에 대해서 앞으로 좀 더 글을 쓰고 싶다. 처음으로 이 길로 들어섰을 때 내가 썼던 것처럼, TypeScript는 좋은 트레이드오프를 만들고 있는 것 같다.


  1. 1.역자 주: left-pad 사건을 말한다. 링크 참조.