우리가 Typescript를 선택한 이유

이글은 redditblog에 실린 Why We Chose Typescript의 번역입니다. 미흡한 번역이지만 도움이 되었으면 합니다


Niranjan Ramadas (u/nr4madas) 
Engineeing Manager

올해 초에 우리 회사의 CEO인 Steve는 사이트를 새로 디자인하고 있다고 말했다. 멋진 일이다! 하지만 어떻게 바꿀 것인가. 프론트엔드 엔지니어링은 Reddit이 처음 세상에 처음 나올 때와는 매우 다른 양상을 보이고 있다. 웹 어플리케이션 개발의 모든 레이어에 대해서 수많은 선택지가 존재하게 되었다. 뷰를 어떻게 그릴까에서 부터 어떻게 스타일을 입힐 지, 리소스들을 어떻게 올릴 지, 코드를 어떻게 작성해야할지 등에 대해서 프론트엔드 개발은 적어도 둘 이상의 선택지를 가지고 있다. 그 중에서 우리가 답을 찾아야 했던 첫번째 질문은 “우리는 어떤 언어를 사용해야할까?”라는 것이었다.

놀랍게도, 그 답이 반드시 Javascript일 필요는 없다. 물론 결국에는 어떤 언어를 선택하든지 간에 Javascript로 컴파일되어 떨어질 것이다. 그렇지만 어떤 언어로 컴파일 될지는 개발자가 어떤 언어로 코드를 작성할 것인지보다 아마도 덜 중요한 문제일 것이다. 선택지는 너무나도 많다.

1. Bucklescript
 2. ClojureScript
 3. Coffeescript
 4. Elm
 5. Elixirscript 
 6. Javascript 2016 and beyond 
 7. Javascript + annotations 
 8. Nim 
 9. PureScript 
 10. Reason 
 11. Typescript

…이 정도만 써보겠다. 각 언어는 장점과 단점을 가지고 있으며, 이 중에서 하나를 선택하기 위해, 우리는 몇가지 요구사항을 정리해보았다.

  1. Type이 있어야 한다. 문서의 micro-level에서 Type이 지원되다면, 정확함을 보장할 수 있고, 가장 중요한 이유로 코드를 refactoring 하는 것이 덜 괴롭게 된다. 다른 고민할 부분은 개발 속도였다. 우리는 빠르게 움직이고 싶었기 때문에 Type들을 사용하고 싶었다. 이 말은 어떤사람들의 생각과는 정반대인 것처럼 들린다. 많은 사람들은 Type은 개발에 오버헤드를 늘리게 되고, 개발자들 역시 느려질 것이라고 생각한다. 그렇지만 빠르게 움직여야 할 때, Type은 버그를 찾기 쉽게 만들어준다. 우리는 빠른 페이스로 움직일 때, 코드의 정확성을 유지하기 위한 방법으로 Type을 고민했다. Type은 또한 코드베이스들의 확장을 돕는다. 우리의 엔지니어링 팀은 빠르게 성장하고 있으며(채용중입니다!) 수많은 사람들의 기여는 계속해서 증가할 것이다.
  2. 좋은 도구들을 이미 가지고 있는 상태여야 한다. 작업(전반적인 재디자인)의 범위를 생각했을 때, 우리는 많은 도구들을 스스로 만들어낼 수 있는 시간은 없다. 우리가 충분히 준비된 오픈소스 솔루션들을 통해 빠르게 시작할 수 있느냐는 꽤 중요한 부분이다. 좀 더 구체적으로 우리가 오픈소스 도구들을 찾은 기준은 유명한 빌드툴(예를 들어, webpack)과의 통합, linting 지원 여부, 테스트 프레임워크와 쉬운 통합이었다. 우리는 언어 도구의 통합이 명확하지 않다면 이를 제외했다.
  3. Powers major production apps. 만약 언어가 멋져보인다고 하더라도, 취미 단계에 머무르는 프로젝트라면 이것은 우리에게 적합하지 않을 것이다. 그런 프로젝트는 오래 지속될 수 있을지 이슈에 대한 수정 요청이 제대로 이루어질지 알 수 없다.
  4. 개발자들이 빠르게 배울 수 있어야한다. 위 리스트에는 몇몇 매우 훌륭하지만 우리 개발자들이 적용하기에는 꽤 시간이 걸리는 언어들이 있다. Elm과 Purescript가 좋은 예이다. 우리는 심각하게 이 언어들을 사용할지 고민했지만, 결국에는 프로덕트의 요구사항을 구현하면서, 언어들에 익숙하지 않은 개발자들에게 새로운 프로그래밍 개념을 가르치는데 너무 많은 시간이 소요될 것 같았다.
  5. 클라이언트와 서버 모두에서 사용가능해야한다. SEO는 reddit의 매우 중요한 목표이다. universal rendering이 불가능하다면 우리는 그 언어를 사용할 수는 없다.
  6. 라이브러리 지원이 잘 이뤄져있어야한다. 우리는 모든 것을 처음부터 만들고 싶어하지 않는다. 문제가 있을 때 라이브러리를 통해 해결할 수 있다면 그것은 최선의 방법일 수 있다. 우리는 이것을 옵션으로 지키고 싶었다.

이러한 요구사항들을 고려했을 때, 우리의 선택지 중 가장 나은 두가지는 TypeScript와 Javscript + Flow로 보였다. 결정을 내리기 전에 우리는 둘 사이에 어떤 점이 다른지를 이해할 필요가 있었다.

컴필레이션 vs 어노테이션

TypeScript와 Flow의 큰 차이점 중 하나는 TypeScript는 Javascript로 컴파일 되는 언어이고, 반면에 Flow는 Javascript위에 작성할 수 있는 annotaion들의 모음으로 도구에 의해서 정확성이 검증되게 된다.

이것은 어떻게 코드를 작성하느냐에 있어서 미묘한 차이를 가져다 준다. 예를 들어 양쪽의 구조 상에서 enum에 대해 살펴보자

TypeScipt

enum VoteDirection {
upvoted = 1,
notvoted = 0,
downvoted = -1,
};
const voteState: VoteDirection = VoteDirection.upvoted;

Flow

const voteDirections = {
upvoted: 1,
notvoted: 0,
downvoted: -1,
};
type VoteDirection = $Keys<typeof voteDirections>;
const voteState: VoteDirection = voteDirections.upvoted;

Typescript가 컴파일되면, 런타임 시에 정의되는 타입들이 생성된다. 그렇지만 Flow에서는 타입들은 단지 어노테이션일 분이며, 그렇기 때문에 우리가 작성한 타입이 런타임 시에 표현되기 위해 코드를 전환하는 것에 의존하지 않는다.

하지만 Typescript의 컴필레이션은 단점을 갖는다. Typescript를 기존의 코드베이스에 통합할 때, Typescript의 컴필레이션은 빌드 프로세스에 복잡함을 가져올 우려가 있다. Reddit에서는 기존의 빌드 과정에서 transpilation 레이어로 Babel을 사용하고 있었다. 우리가 Typescript를 추가하려고 할 때 기존의 것을 유지하기 위해 몇가지 최적화가 필요했고, 그래서 우리는 이미 가지고 있던 것들과 큰 충돌없이 Typescript를 통합할 수 있는 방법이 필요했다. 그리고 결과적으로 더 복잡한 빌드 단계를 만들 수 밖에 없었다.

대조적으로, Flow의 타입 어노테이션은 Babel에 의해서 자동적으로 제거가 된다. 그래서 Flow를 적용했을 때에 우리의 빌드 과정은 여전히 단순할 수 있었다.

안정성/정확성

Flow는 이 부분에서 일반적으로 더 나은 모습을 보여준다. 디폴트로 Flow는 nullable 타입을 허용하지 않는다. Typescript는 2.x 버전부터 non-nullable 타입에 대한 지원을 시작했지만, 이에 대해 알아두어야 할 것이 있다. Flow는 타입 추론에 있어서 더 나은 모습을 보여주며, Typescript “any” 타입으로 fallback되는 경우가 많다.

nullability와 타입 추론 외에도, Flow는 공변성과 반공변성에서도 더 나은 모습을 보여준다.(이 링크에서 Flow의 가변성에 관해 더 많은 것을 볼 수 있다.) 다음은 이러한 점에 대해 설명해줄 수 있는 array에서의 이슈에 관한 예제이다. Flow에서의 디폴트 array는 가변적이지 않다. Flow에서 이것은 에러를 던질 것이라는 의미이다.

Flow

class Animal {}
class Bird extends Animal {}

const foo: Array<Bird> = [];

foo.push(new Animal());
/*
foo.push(new A);
^ A. This type is incompatible with
const foo: Array<B> = [];
^ B
*/

그렇지만 Typescript에서는 문제없이 동작한다.

Typescript

class Animal {}
class Bird extends Animal {}

const foo: Array<Bird> = [];

foo.push(new Animal()); // ok in typescript

이와 관련하여 온라인 상에서 더 많은 예제를 찾을 수가 있을 것이다. 그렇지만 의견은 type-checking에 있어서 Flow가 Typescript에 비해 더 나은 모습을 보여준다는 것이다.

생태계

지금까지 알아본 바로는 Flow의 도입이 더 단순해보이며, type-checking에 있어서도 더 나은 것으로 보여진다. 그렇다면 왜 우리는 Typescript를 선택했을까?

Typescript의 강점 중 하나는 생태계이다. Typescript의 라이브러리 지원은환상적이다; 우리가 사용하는 모든 라이브러리들은 라이브러리 자신 안에 type descriptor를 가지고 있거나 representation in DefinitelyTyped를 가지고 있다. 추가적으로 Typescript는 VSCode 상에서 Intellisense를 잘 지원받으며, 우리가 사용하는 다른 유명한 에디터 상에서(Atom 이나 Sublime Text 같은) 플러그인 지원이 가능하다. 게다가 우리는 Typescript가 JSDoc 어노테이션들도 파싱한다는 것을 발견했다. 이것은 Javacript에서 Typescript로 전환할 때 매우 유용한 것이었다.

?Typescrip는 롱런할 것이라는 많은 “사회적 증거(Social Proof)” 와 자신감이 있다. 몇몇 큰 규모의 프로젝트(VSCode, Rxjs, Angular 그리고 Typescript 자신 들은 Typescript를 사용하고 있다. 그래서 우리는 Typescript의 기능들이 우리 제품의 목적을 잘 지원하고 언어가 몇년 동안은 지속될 수 있을 것이라고 느꼈다. 우리가 Flow에 대해 가졌던 염려 중에 하나는 이것이 Facebook에서의 특정한 요구사항을 해결하기위에 만들어졌고, 언어의 미래 역시도 그러한 범주 안에서 결정될 것이라는 점이었다. 반면에 Typescript는 Microsoft에서 ?좀 더 일반적인 목적으로 만들어졌고, 우리가 이슈를 제기하면 그들이 들어줄 것이라고 생각했다.

추가적으로 Typescript는 Javascript의 superset으로 존재하는 언어기 때문에 우리는 ES6+ 의 기능들을 Typescript가 지원하여 해당하는 타입의 형태로 사용가능해질 것이라는 기대를 했다. 언어와 언어의 타입 시스템은 함께 개발될 것이기 때문에l ock-step으로 움직일 것이란 것이 이유였다.

요약

우리는 타입스크립트를 선택한 이유는 빠르게 우리 시스템에 적용할 수 있을 것이라는 자신감이 있었기 때문이고(지난해에 프론트엔드 엔지니어의 숫자는 세배가 되었다.) 사이트의 모든 것을 다시 디자인하려는 프로덕트의 목표를 지원할 수 있어보였기 때문이다. 시간은 조금 걸리겠지만, Typescript는 우리의 코드베이스에 잘 통합될 것으로 보인다. 그렇지만 가장 중요한 것은 우리가 언어를 타입을 가지고 있는 언어로 변경했다는 점이다. 타입이 있는 언어를 프론트엔드에서 사용하면서 우리는 이미 그 효과를 누리고 있다. 우리의 코드에서 타입과 관련된 에러는 적어젔고, 우리는 대규모의 리펙터링을 하는 것에 대해 자신감이 생겼으며 우리의 인라인 문서는 object의 형태와 function의 파라미터 대신에 개념에 초점을 맞추고 있다. 전체적으로 우리는 우리의 선택에 매우 만족하고 있다.