Занимательная история от Феликса Ризеберга, разработчика в Slack, о том как они используют JavaScript, почему перешли на TypeScript и какие подводные камни встретились на их пути.
Когда Брендан Айх (Brendan Eich) создал самую первую версию JavaScript для Netscape Navigator 2.0 всего за десять дней, он, скорее всего, не ожидал, что его изобретение станет столь популярным благодаря классическому приложению Slack. Мы используем единую базу кода JavaScript для создания многопоточного классического приложения для ОС Windows, MacOS и Linux, которое осуществляет постоянное взаимодействие с машинным кодом.
Управление большими базами кода JavaScript представляется довольно сложной задачей. Каждый раз при передаче объектов из JavaScript в Chrome в Objective-C отдельные части кода должны гарантированно соответствовать друг другу для получения обратного вызова в другом потоке Node.js. В настольных операционных системах самая незначительная ошибка может привести к сбою приложения. Именно по этой причине мы внедрили TypeScript (расширенный набор кодов JavaScript на основе статических типов), после чего быстро научились не беспокоиться и доверять компиляторам. И мы не одиноки. По результатам опроса разработчиков StackOverflow в 2017 году TypeScript занял почетное третье место среди самых популярных технологий программирования. Учитывая, насколько быстро статическая проверка типов набирает популярность, нам хотелось бы поделиться своими знаниями и практическим опытом.
Статический анализ спешит на помощь
В прошлом мы применяли JSDoc для документирования подписей функций, а также использовали комментарии, чтобы сообщить разработчикам о цели и назначении классов, функций и переменных. В этом тоже есть свои минусы. С первого взгляда на код трудно понять, к чему приведет использование JavaScript. Приходится просто верить на слово, что разработчик, написавший этот код, правильно его задокументировал, и все те, кто впоследствии этот код изменял, должным образом отразили эти изменения в документации. В сложных системах, включающих множество модулей и зависимостей, можно запросто получить сбой в функции, даже не открывая файл, в котором она содержится.
Чтобы как-то улучшить ситуацию, мы решили попробовать применить статическую проверку типов. Инструмент статической проверки типов не изменяет поведение кода в среде выполнения. Вместо этого он анализирует код и пытается по возможности логически выводить типы, направляя разработчику предупреждение перед отсылкой кода. Инструмент статической проверки типов понимает, что Math.random()
возвращает число, не содержащее строкового метода toLowerCase()
.
Иными словами, при использовании такого инструмента обеспечивается поддержка системы за счет объявления типов вручную, что позволяет сообщать о предполагаемом поведении программы как пользователю, так и компьютеру. Приведенный ниже код определяет интерфейс для объекта «user» и метода, который предположительно должен получать данные о возрасте пользователя. Инструмент статической проверки типов анализирует этот код и предупреждает о стандартных ошибках (например, когда пользователь ожидает, что неопределенное свойство всегда будет доступно).
Что интересно, код не изменяется в среде выполнения: инструмент статической проверки типов не предоставляет служебных данных конечному пользователю. Приведенный выше пример в среде выполнения выглядит как совершенно стандартный код JavaScript:
Интеллектуальный инструмент статической проверки типов позволяет быть уверенным в качестве кода, дает возможность выявлять типичные ошибки еще до того, как они совершены, а также обеспечивает автоматизированное документирование базы кода.
Перенос базы кода классического приложения Slack в TypeScript
Мы решили использовать программу TypeScript корпорации Microsoft, в которой функции статического анализа типов объединены с компилятором. Современный JavaScript — это действующий TypeScript, то есть TypeScript можно использовать, не меняя ни одной строчки кода. Таким образом, можно использовать «постепенную типизацию», чтобы выполнять компиляцию и статический анализ на ранних этапах и не прерывать работу на время устранения критических ошибок или добавления новых функций.
На практике включение функций анализа и компилятора без изменения кода означает, что TypeScript сразу же пытается проанализировать и расшифровать код. Программа использует встроенные типы и определения типов, доступные для сторонних зависимостей, чтобы проанализировать поток кода и выявить те мелкие ошибки, которые ранее остались незамеченными. Если программе TypeScript не удалось успешно расшифровать код, она относит его к особому типу «any» и просто идет дальше.
Изначально мы планировали постепенно переносить файлы, по возможности просто расширяя стандартный код JavaScript за счет добавления более конкретных определений типов: добавление интерфейсов, определение методов классов как частных или общих методов, а также объявление перечислений. В процессе мы сделали два удивительных открытия:
Во-первых, нас удивило количество мелких ошибок, обнаруженных во время преобразования кода. Обсуждая этот момент с другими разработчиками, которые тоже начали использовать инструмент проверки типов, мы поняли, что это типичная ситуация: когда разработчик пишет очень много строк кода, в итоге практически неизбежно допускаются опечатки в свойстве, предполагается, что родительский объект для вложенного объекта существует во всех случаях, или используется нестандартный объект «error».
Во-вторых, мы недооценили возможности интеграции редактора. Благодаря языковой службе TypeScript редакторы, поддерживающие функцию автозаполнения, также поддерживают разработку на основе контекстно-зависимых вариантов. TypeScript понимает, какие свойства и методы доступны для отдельных объектов, и редактор, таким образом, получает ту же информацию. Система с функцией автозаполнения, которая использует только слова в текущем документе, после этого выглядит уже довольно примитивной. Прошли те времена, когда нам приходилось постоянно нырять в Google, чтобы проверить еще разок, какие события доступны в окне браузера Electron. Для Atom, Visual Studio Code, Sublime и практически любого другого редактора доступны подключаемые модули. Благодаря возможности проверить код, не закрывая редактор, производительность работы существенно повысилась.
В применении к обслуживанию кода перспективы экосистемы TypeScript выглядят весьма впечатляюще. Тем, кто активно использует экосистему React и Node/npm, доступность определений типов для сторонних библиотек дает огромные преимущества. Многие из импортируемых библиотек уже поддерживают TypeScript. Если определения не предоставляются вместе с модулем, скорее всего, их можно будет найти в проекте DefinitelyTyped. React, например, не предоставляет определений типов, в то время как простой пакет npm install @types/react
устанавливает такие определения по умолчанию, и последующая конфигурация уже не потребуется.
Программа TypeScript стала настоящей находкой, обеспечивая стабильность в процессе работы и качественный результат, и в итоге через несколько дней с начала преобразования мы начали применять ее для всего нового кода. Аннотирование большей части кода JavaScript в базе кода классического приложения заняло примерно шесть месяцев.
Уверенная фиксация
Чтобы обеспечить понимание кода и возможность его обслуживания, весь размещенный код перед фиксацией автоматически проверяется с помощью TSLint. Таким образом, перед тем как Git зафиксирует внесенные изменения, сначала проверяется, что измененный код соответствует правилам проверки стиля оформления кода. Использование типа «any» явным образом не допускается: весь код приложения Slack должен явным образом указывать тип во всех случаях, когда TypeScript не может логически вывести его самостоятельно.
Когда необходимо создать ветвление, Git сначала исполняет всю базу кода в компиляторе TypeScript, который анализирует ее на предмет ошибок в структуре и функциях, а затем компилирует существующие функции (например, async/await) в код, совместимый с ES2016, на другом языке. К моменту создания запроса на включение внесенных изменений уже есть полная уверенность в том, что структурные зависимости в коде корректны.
Умение приходит не сразу
Преимущества TypeScript однозначно перевешивают все реальные недостатки. Самым серьезным минусом в нашем случае можно считать дополнительные затраты на обучение. Разработчики, у которых есть опыт работы со строго типизированными языками, как правило, способны разобраться в синтаксисе в течение пары часов. Однако файл, в котором полноценно используются все функции TypeScript, может поставить в тупик тех, кто привык работать со стандартными кодами JavaScript.
Наиболее очевидным решением в данном случае было бы поэтапное представление функций. Можно просто включить TypeScript, не изменяя код, добавить несколько простых объявлений типов и сохранить более сложные схемы — наследование, универсальные шаблоны и расширенные типы (например, типы пересечений, сопоставленные типы) в отдельных модулях или на более позднем этапе. В конечном счете можно получить массу преимуществ, даже если используются только базовые функции TypeScript.
Внести свой вклад
Slack ориентирован на технологии с открытым исходным кодом. И именно поэтому мы пытаемся сделать переход других разработчиков на TypeScript как можно более легким и комфортным. Все обнаруженные пробелы мы стремимся восполнить в кратчайшие сроки.
В частности, собственный компилятор Slack (компилятор Electron) позволяет разработчикам приложений Electron писать код в TypeScript, не беспокоясь о последующей компиляции. Библиотека RxJS, которая активно используется Slack, Nemutflix, GitHub и многими другими компаниями, при поддержке Slack теперь тоже перешла на TypeScript. Многие небольшие библиотеки, написанные нашими разработчиками классических приложений, медленно, но верно переходят к использованию TypeScript (например, spawn-rx, electron-spellchecker, electron-remote, electron-notification-state и electron-windows-notifications).
Перед началом работы с TypeScript ознакомьтесь с официальным руководством. Хороший практический пример малого порта можно посмотреть в описании порта spawn-rx. Отличный инструмент, который поможет успешно написать первые строки кода TypeScript для приложений Electron: electron-forge, где задействован компилятор Electron с поддержкой TypeScript. В нем есть даже удобный шаблон React/TypeScript с архитектурой, столь любимой нашей командой разработчиков приложений Slack. Если комбинирование веб-технологий с машинным кодом в целях создания кроссплатформенных классических приложений кажется вам интересной задачей, мы с нетерпением ждем вас!
Автор: Schvepsss