Примерно полгода назад CEO Reddit Стив сообщил о том, что мы перепроектируем сайт. Главный вопрос тут — как именно мы этим занимаемся. В наше время фронтенд-разработка очень сильно отличается от того, что было во времена, когда Reddit появился на свет. Сейчас имеется огромный выбор вариантов для каждой подсистемы веб-приложения. Как рендерить страницы? Как стилизовать контент? Как хранить и обслуживать картинки и видеофайлы? Как писать код? В современных условиях ни на один из этих вопросов нет готового ответа.
Одним из первых подобных вопросов, на который нам необходимо было найти ответ, звучал так: «Какой язык выбрать?».
О богатстве выбора и требованиях к языку
Как ни странно, нашим языком для фронтенда не обязательно должен был стать JavaScript. В конечном счёте, какой бы язык для этой цели ни был бы выбран, код, написанный на нём, всё равно компилируется в JavaScript. Однако, во что именно компилируется код, возможно, менее важно, чем то, что именно пишет разработчик. Выбор языка оказался делом непростым. Вот что нам пришлось рассмотреть:
- BuckleScript
- ClojureScript
- CoffeeScript
- Elm
- ElixirScript
- JavaScript 2016 и будущие версии языка
- JavaScript + аннотации
- Nim
- PureScript
- Reason
- TypeScript
И это, кстати, далеко не полный список. У каждого языка есть свои плюсы и минусы, поэтому для того, чтобы помочь самим себе выбрать один из них, мы сформулировали следующие требования:
- Это должен быть язык со строгой типизацией. Типы, на микроуровне, играют роль документации. Они помогают, до определённой степени, обеспечивать правильность кода, и, что важнее, упрощают рефакторинг. Ещё одним соображением, в силу которого мы искали язык со строгой типизацией, была скорость разработки. Мы стремились найти строго типизированный язык, так как хотели ускорить работу. Такая идея идёт вразрез с тем видением типизации, которое сложилось у многих программистов. А именно, принято считать, что это — дополнительная нагрузка на разработчика, которая снижает скорость работы. Однако повышение скорости разработки означает ещё и увеличение вероятности появления ошибок. Нам нужна была типизация для того, чтобы код содержал как можно меньше ошибок, даже если пишут его быстро. Строгая типизация, кроме того, полезна в быстрорастущих проектах. Наша команда инженеров постоянно увеличивается в размерах, и число тех, кто работает над кодом, стабильно растёт.
- Нужно, чтобы существовали хорошие вспомогательные инструменты. Учитывая то, чем мы собирались заниматься (серьёзное перепроектирование сайта), у нас не было времени на то, чтобы самостоятельно создавать значительное число вспомогательных средств. Было очень важно, чтобы мы могли быстро приступить к работе, используя готовые решения с открытым кодом. Если говорить конкретнее, то вот о каких именно инструментах идёт речь. Это — интеграция с популярными системами сборки (например, с Webpack), поддержка линтера, простая интеграция с фреймворками для тестирования. Если интеграция вспомогательных систем для какого-то языка была неочевидной, мы больше этот язык не рассматривали.
- Язык должен был использоваться в серьёзных проектах. Если язык выглядел хорошо, но в реальности использовался лишь в мелких проектах отдельных энтузиастов, он вполне мог оказаться неподходящим для нашей задачи. Кроме того, если язык не используется в серьёзных проектах, возникают вопросы о том, долго ли такой язык просуществует, и о том, как быстро разработчики языка будут реагировать на сообщения о найденных в нём ошибках.
- Наши разработчики должны освоить язык достаточно быстро. В вышеприведённом списке есть прекрасные языки, единственный минус которых — слишком большой срок, который нужен разработчикам для того, чтобы их освоить. Среди них хочется отметить Elm и PureScript. Мы серьёзно обсуждали вопрос их использования. Однако, в итоге оказалось, что их внедрение означало бы слишком большой объём работы, необходимый для освоения новых концепций программирования разработчиками, которые с ними не знакомы, для выхода их на уровень, позволяющий продуктивно работать над проектом.
- Язык должен работать и на клиенте, и на сервере. На Reddit очень важно SEO, поэтому отсутствие универсальной системы выдачи готовых к отображению в браузере страниц — это большая проблема.
- Хорошая поддержка библиотек. Мы не собирались писать всё с нуля. Существуют некоторые задачи, для решения которых нужны надёжные, проверенные временем библиотеки. Нам хотелось, чтобы у нас была возможность выбора того, что нам нужно, из существующих библиотек.
После рассмотрения этих требований мы остановились на двух вариантах. Первый — TypeScript. Второй — JavaScript + Flow. Но, прежде чем сделать окончательный выбор, мы хотели как можно лучше понять особенности TypeScript и Flow, а также различия между ними.
Компиляция или аннотирование?
Одно из важных различий TypeScript и Flow заключается в том, что TypeScript — это язык, который компилируется в JavaScript, а Flow — это набор аннотаций типов, которые можно добавлять к JavaScript-коду. Корректность аннотированного кода проверяет статический анализатор.
Вышеописанные различия прямо влияют на то, как именно пишут программы. Взгляните, например, на работу с перечислениями в TypeScript и Flow.
TypeScript
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 в существующую кодовую базу есть вероятность того, что компилятор усложнит процесс сборки. У нас есть устоявшийся процесс сборки с использованием Babel и слоя транспиляции. Имеется несколько оптимизаций, которые хотелось бы сохранить даже при переходе на TypeScript, поэтому нужен был способ интеграции TypeScript, который не разрушил бы ту схему работы, которая уже есть. Результатом внедрения TypeScript стал бы гораздо более сложный процесс сборки.
В противовес обработке TypeScript, Babel автоматически удаляет аннотации типов Flow. Если бы мы выбрали Flow, процесс сборки приложения не усложнился бы.
Проверка корректности кода
В области проверок корректности кода Flow обычно показывает себя лучше, чем TypeScript. Во Flow, по умолчанию, запрещается использовать типы, допускающие значение NULL
. В TypeScript 2.x была добавлена поддержка типов, в которых не допускается NULL
, однако, эту возможность нужно включать самостоятельно. Кроме того, Flow лучше выводит типы, в то время как TypeScript часто обращается к типу any
.
Помимо типов, допускающих значение NULL
и вывода типов, Flow лучше в вопросах ковариантности и контравариантности (вот материал на эту тему). Одна из типичных проблемных ситуаций здесь — работа с типизированными массивами. По умолчанию массивы во Flow инвариантны. Это означает, что следующая конструкция во 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()); // в typescript всё нормально
Можно найти ещё много похожих примеров, однако, общий вывод заключается в том, что Flow гораздо лучше показывает себя в деле проверки типов, нежели TypeScript.
Экосистема
Всё, что было сказано до сих пор, говорит о преимуществах Flow. Его легче интегрировать в проект, он лучше справляется с проверкой типов. Почему же мы остановились на TypeScript?
Одно из важнейших преимуществ TypeScript — его экосистема. Он отличается потрясающей поддержкой библиотек. Практически все библиотеки, которыми мы пользовались, либо имеют описания типов в самих библиотеках, либо представлены в DefinitelyTyped. Кроме того, TypeScript обладает отличной поддержкой IntelliSense в VSCode и в плагинах для других популярных редакторов, которыми мы пользуемся (например, среди них — Atom и SublimeText). Более того, мы обнаружили, что TypeScript умеет обрабатывать аннотации JSDoc. Это оказалось особенно полезным, так как мы переходили на TypeScript с JavaScript.
Кроме того, TypeScript отличается большей «социальной значимостью», и есть ощущение, что срок его жизни будет достаточно долгим. Существует несколько крупных проектов, использующих TypeScript (среди них — VSCode, Rxjs, Angular, да и сам TypeScript), поэтому у нас имеется уверенность в том, что набор его возможностей сможет соответствовать целям нашего проекта, и в том, что язык в ближайшие годы никуда не денется. По поводу же Flow нас беспокоит то, что он был создан для решения специфических задач в Facebook, и то, что его развитие будет определяться тем же диапазоном задач. TypeScript, с другой стороны, ориентирован на широкий спектр проблем, работой над ним занимается Microsoft. Всё это позволяет нам предполагать, что если мы столкнёмся с ошибками и сообщим о них, нас услышат и примут меры.
Кроме того, так как TypeScript — это язык, являющийся надмножеством JavaScript, мы можем ожидать, что развитие TypeScript, в частности, поддержка им новых типов и языковых конструкций, будет следовать за развитием JS. Язык и система типов будут развиваться параллельно, так как работа над ними идёт одновременно.
Итоги
Мы выбрали TypeScript, так как уверены в том, что сможем быстро обучать всему необходимому новых разработчиков (число наших фронтенд-инженеров утроилось за последний год). Мы уверены в том, что язык соответствует нашим запросам в деле перепроектирования сайта. Кроме того, всё говорит о том, что TypeScript будет существовать ещё долго, а наши изыскания показали, что он хорошо интегрируется с нашей кодовой базой. Однако, для нас самое главное это переход на строго типизированный язык.
Использование типизированного языка уже принесло некоторые результаты. В коде стало меньше ошибок, связанных с типами, мы увереннее выполняем большие рефакторинги. Встроенная документация теперь сосредоточена на концепциях, а не на описании внутреннего устройства объектов и параметров функций. В результате мы можем сказать, что TypeScript — это именно то, что нам было нужно.
Уважаемые читатели! Пользуетесь ли вы TypeScript или Flow?
Автор: RUVDS.com