Или как мы перестали беспокоиться и научились доверять компилятору
Когда Брендан Эйх создал самую первую версию JavaScript для Netscape Navigator 2.0 всего за десять дней, вряд ли он ожидал, в какой степени Slack Desktop App будет использовать его изобретение. Мы используем только кодовую базу JavaScript для многопоточного десктопного приложения, которое постоянно взаимодействует с нативным кодом и работает под Windows, macOS и Linux.
Управлять большими кодовыми базами JavaScript непросто. Всякий раз, когда мы мимоходом передаём объекты из JavaScript браузера Chrome в Objective-C, чтобы просто получить обратный вызов через другой поток на Node.js, нужна гарантия, что все кусочки складываются вместе. В десктопном мире маленькая ошибка может привести к сбою приложения. С этой целью мы внедрили TypeScript (статически типизированное надмножество JavaScript) и быстро поняли, как жить без волнений и с любовью к компилятору. И не только мы: опрос разработчиков на Stack Overflow показывает, что TypeScript является третьей самой любимой технологией программирования. Учитывая, насколько быстро статическая проверка типов набирает ход, мы хотим поделиться нашим опытом и методиками.
Статический анализ приходит на помощь
В прошлом мы использовали JSDoc, чтобы документировать сигнатуры наших функций. Мы в комментариях объясняли цель и способ правильного использования классов, функций и переменных. Такой способ не был лишён своих проблем. Глядя на сам код трудно понять, как разрешится промис JavaScript. Вам придётся довериться, что автор кода всё правильно задокументировал, а тот, кто позже вносил правки в код, корректно обновил документацию. В сложных системах с бесчисленными модулями и зависимостями очень легко сломать функцию даже не открывая файл, в котором она записана.
Чтобы исправить ситуацию, мы решили дать шанс статической проверке типов. Статическая проверка не меняет выполнение вашего кода — вместо этого, она анализирует код и пытается вычислить типы где только возможно, предупреждая разработчика перед сдачей программы.
Статическая проверка типов понимает, что Math.random()
возвращает число, которое не содержит строковый метод toLowerCase()
.
Чтобы более явно проявить намерения, пользователь такого контролёра типов может помочь системе, вручную объявляя эти типы — чтобы сообщить и людям, и машине, как должна себя вести программа. Код ниже определяет интерфейс для объекта “user” и метод, который предположительно должен получить возраст пользователя. Статическая проверка типов способна провести анализ такого кода и предупредить о типичных человеческих ошибках, таких как ожидание постоянного присутствия свойства, которое может быть undefined
.
Интересно, что код не изменяется во время выполнения, то есть статическая проверка типов не накладывает никаких дополнительных расходов на конечного пользователя. Вышеупомянутый пример при выполнении выглядит как классический JavaScript:
Умный контролёр типов увеличивает нашу уверенность в коде, вылавливает распространённые ошибки до отправки программы в производство и позволяет кодовой базу лучше документировать саму себя.
Портирование кодовой базы Slack Desktop на TypeScript
Мы решили использовать Microsoft TypeScript, где сочетается статический анализ с компилятором. Современный JavaScript — это валидный TypeScript, то есть вы можете использовать TypeScript не меняя ни единой строки кода. Это позволило нам реализовать «постепенное типирование», включив компилятор и статический анализ на ранней стадии, не приостанавливая работу над критическими багами или новыми функциями.
На практике включение анализа и компилятора без изменения кода означает, что TypeScript немедленно попытается понять ваш код. Он использует встроенные типы и определения типов, доступные для сторонних зависимостей, чтобы проанализировать поток кода, указывая на трудноуловимые ошибки, которые раньше никто не заметил. Если TypeScript не может понять ваш код, он просто предположит специальный тип под названием any
, и пойдёт дальше.
Наш изначальный план состоял в том, чтобы медленно портировать файлы один за другим, расширяя стандартный JavaScript более конкретными определения типов там, где это возможно — добавляя интерфейсы, определяя методы классов как приватные или публичные и объявляя перечисления (enum). По ходу дела мы сделали два неожиданных открытия:
Во-первых, нас удивило количество маленьких багов, найденных во время преобразования кода. Поговорив с другими разработчиками, которые начали использовать проверку типов, мы с радостью услышали, что так происходит у всех: чем больше строк кода пишет человек, тем неизбежнее он допустит опечатку в свойстве, предположит постоянное существование родительского или вложенного объекта или использует нестандартный ошибочный объект.
Во-вторых, мы недооценили, насколько здесь мощная интеграция с редактором. Благодаря лингвистическому сервису TypeScript редакторы с автодополнением могут поддерживать программирование с контекстными подсказками. TypeScript понимает, какие свойства и методы доступны для определённых объектов, так что редактор теперь тоже понимает. Система автодополнения, которая подсказывает только слова из текущего документа, после этого кажется варварской. Осталось в прошлом время, когда мы опять искали в Google, какие события доступны для Electron BrowserWindow. Есть плагины для Atom, Visual Studio Code, Sublime и почти всех остальных редакторов. Возможность проверять код прямо в редакторе немедленно повысила нашу производительность.
Заглядывая в будущее и думая о поддержке кода, мы ценим экосистему вокруг TypeScript. Для нас как активных пользователей экосистемы React и Node/npm огромным плюсом является доступность определений типов для сторонних библиотек. Многие из импортированных библиотек уже совместимы с TypeScript. Если определения не поставляются с самим модулем, их вероятно можно найти в фантастическом проекте DefinitelyTyped. Например, React не поставляется с определениями типов, но они устанавливаются простой командой npm install @types/react
без необходимости в дальнейшей конфигурации.
TypeScript настолько улучшил стабильность и наше душевное равновесие, что в течение нескольких дней после начала миграции мы стали использовать его для всего нового кода. Понадобилось примерно шесть месяцев, чтобы аннотировать большинство кода JavaScript для десктопного приложения Slack.
Коммиты с уверенностью
Чтобы улучшить читабельность и удобство обслуживания, весь код в среде разработки автоматически проверяется TSLint перед коммитом — это значит, что перед добавлением изменений в Git код сначала проверяется на предмет некорректных выражений по нашим правилам. Мы не разрешаем использование опции implicit any
, то есть во всём коде Slack Desktop должен быть явно указан тип, если TypeScript не может автоматически определить его.
Когда приходит время отправлять изменения в ветку, Git сначала пропускает всю кодовую базу через компилятор TypeScript, который анализирует весь код на структурные и функциональные ошибки и переводит современные функции вроде async/await в ES2016-совместимый код. Ко времени открытия пулл-реквеста у нас уже есть уверенность, что в коде правильные структурные зависимости.
Это может показаться страшным
Для нас преимущества TypeScript кардинально перевешивают недостатки — которые тоже существуют. Наиболее ощутимыми для нас стали расходы на обучение. Разработчики со знанием языков строгой типизации обычно осваивали синтаксис в течение часа или двух, но файл со всеми функциями TypeScript может обескуражить программиста с опытом только чистого JavaScript.
Самым очевидным решением этой проблемы стало медленное выкатывание изменений — вы просто можете использовать TypeScript, не меняя никакого кода, добавлять некоторые простые декларации типов и оставлять более сложные концепции вроде наследования, параметризованных типов и продвинутых типов (пересечение, отображаемые типы — mapped types) или для специфических модулей, или для более позднего этапа типизации. В конечном счёте, опыт показал, что даже минимальное использование TypeScript даёт массу преимуществ.
Отдать обратно
В Slack мы стремимся быть порядочными участниками сообщества open-source. В конце концов, мы хотим сделать TypeScript проще для других разработчиков. Если мы видим пробелы, то стараемся заполнить их.
Прежде всего, собственный пакет electron-compile
от Slack позволяет разработчикам Electron Apps писать на TypeScript, не беспокоясь о компиляции. RxJS, библиотека Reactive Extension, которая активно используется в Slack, Netflix, GitHub и многих других компаниях, перешла на TypeScript при содействии Slack. Много маленьких библиотек, написанных нашими разработчиками, постепенно получают поддержку TypeScript (как spawn-rx, electron-spellchecker, electron-remote, electron-notification-state и electron-windows-notifications).
Чтобы начать работу с TypeScript, смотрите официальное руководство. Если хотите узнать, как на практике выглядят маленькие портированные проекты, взгляните на порт spawn-rx. Если желаете написать свои первые строчки на TypeScript для приложения Electron, используйте великолепный electron-forge, в котором реализован electron-compile и поддержка TypeScript прямо из коробки — он даже поставляется с отличным шаблоном React/TypeScript, эту архитектуру очень любит наша группа разработки Slack Desktop. Если совмещение современных веб-технологий с нативным кодом для разработки кроссплатформенных приложений вам кажется увлекательным делом, приглашаем к нам на работу!
Автор: m1rko