Публикуем перевод статьи, в которой подробно описана многолетняя работа команды по созданию и поддержанию большого портала данных на JavaScript.
В 2019 была написана статья о поддержке больших приложений на JavaScript (Maintaining large JavaScript applications). В продолжение этого материала, хотели бы поделиться клиентским проектом, который моя команда поддерживает с 2014 года.
Портал данных для Организации экономического сотрудничества и развития (ОЭСР)
Стартовая страница портала
Организация экономического сотрудничества и развития (Organisation for Economic Co-operation and Development) — это межправительственный орган, который собирает данные и публикует исследования от имени государств-членов. На портале организации содержится информация из различных сфер: экономика, экология, образование и др.
Портал данных ОЭСР является главным хранилищем статистических данных. Он помогает исследователям, журналистам и политикам находить важную информацию и быстро визуализировать ее с помощью диаграмм. Портал ОЭСР объединяет также библиотеку с публикациями OECD iLibrary, и ресурс OECD.Stat, где хранятся все данные.
ОЭСР финансируется государствами-членами, то есть налогоплательщиками, как мы с вами. Одно из требований проекта — использование экономически эффективных и надежных технологий, так как поддержка кода важна на долгое время.
Портал данных — это совместная работа сотрудников ОЭСР и внешних разработчиков и дизайнеров. Первоначальный дизайн и прототип были созданы Морицем Штефанером и командой Raureif. А компания 9elements разработала front-end часть и до сих пор ее поддерживает.
Сложная кодовая база JavaScript
Самая сложная часть фронтенда на этом портале — это JavaScript-движок для построения диаграмм. Он содержит десять основных типов диаграмм с многочисленными настройками конфигурации. Используя мощные интерфейсы, юзеры могут запрашивать базу данных и создавать диаграммы для встраивания на сайт или совместного использования.
Мы начали работу над порталом данных в 2014 году. С тех пор его особо не переписывали, только добавляли новые функции, небольшие улучшения и рефакторили код. В декабре 2020 для отчета OECD Economic Outlook мы добавили несколько новых функций, в том числе еще четыре типа диаграмм. Кроме того, в тот раз мы значительно реорганизовали кодовую базу.
В этой статье я расскажу, как нам удается поддерживать код так долго и шаг за шагом улучшать его. А также раскрою и то, что не получилось.
Скучная мейнстримная технология
Проект стартовал в 2014 году, и тогда мы выбрали простой HTML, шаблоны XSLT, Sass для стилей таблиц и CoffeeScript как язык, который компилируется с JavaScript. В качестве библиотек JavaScript мы выбрали jQuery, D3, D3.chart и Backbone.
В 2014 году эти технологии были самыми безопасными и наиболее совместимыми, из всех них только выбор CoffeeScript был рискованной авантюрой. Благодаря CoffeeScript, мы смогли стать более продуктивными и написали надежный код. Но мы знали, что с этой новой технологией могут возникнуть трудности.
С 2015 года команда 9elements стала использовать React для большинства веб-приложений на JavaScript. Мы думали использовать React для новой версии движка диаграмм, но все не могли выбрать подходящий момент. В результате оказалось, что придерживаться исходного стека технологий было правильным решением.
Сейчас описанный стек JavaScript может показаться устаревшим, но, на самом деле, кодовая база выдержала испытание временем. Одна из причин: выбранные нами технологии до сих пор релевантны.
Разрушительное влияние времени
Множество JavaScript-библиотек появлялось и исчезало, но jQuery по-прежнему остается самой популярной. Она надежна, хорошо поддерживается и широко распространена. Согласно Web Almanac 2020, jQuery используется на 83% всех веб-сайтов. (Для сравнения, React обнаружили только на 4%.)
Конечно, jQuery утратила лидерские позиции в решении сложных задач с DOM. Как уже упоминалось, сейчас для создания подобного портала данных мы бы выбрали React или Preact.
Вторая библиотека, D3, остается стандартом в области визуализации данных в браузере. Она существует с 2010 года и до сих пор остается лидером. Хотя пара крупных релизов значительно изменили структуру и API, D3 — это все еще выдающаяся инженерная работа.
Библиотека Backbone не так популярна, но имеет свои плюсы. Например, она относительно простая: вы можете прочитать исходный код за одно утро и переделать основные части за день. Кроме того, Backbone все еще поддерживается. Она полнофункциональна, что особенно важно.
С технологической точки зрения только CoffeeScript не является актуальной технологией в текущих реалиях. Данный язык был разработан из-за явных недостатков в ECMAScript 5. Позже многие идеи из CoffeeScript были включены в стандарты ECMAScript 6 (2015) и ECMAScript 7 (2016). С этого момента у нас уже не осталось причин его использовать.
Мы выбрали CoffeeScript в 2014 году из-за его философии: «Это просто JavaScript». В отличие от других языков, компилируемых с JavaScript, CoffeeScript представляет собой прямую абстракцию. Код CoffeeScript без сюрпризов компилируется в чистый JavaScript.
Сегодня большинство компаний перенесли свои кодовые базы с CoffeeScript на современный JavaScript. И мы сделали то же самое.
От CoffeeScript до TypeScript
С помощью этого инструмента для декофеинизации, мы преобразовали код CoffeeScript в ECMAScript 6 (2015). Мы хотели продолжить поддерживать те же самые браузеры, поэтому теперь используем компилятор Babel для создания обратно совместимого ECMAScript 5.
В целом переход прошел гладко, но мы не хотели останавливаться на достигнутом.
В новых проектах разработчики из 9elements используют TypeScript. На мой взгляд, TypeScript — лучшее, что произошло в мире JavaScript за последние пару лет. Как я упоминал в своей предыдущей статье, TypeScript заставляет задуматься о типах и приучает правильно их называть.
Для нашего портала данных мы собирались использовать преимущества TypeScript без конвертации кодовой базы в полностью типизированный TypeScript.
TypeScript — это надмножество JavaScript. Компилятор хорошо понимает файлы .js. Поэтому мы постепенно добавляли аннотации типов с помощью технологии 20-летней давности — JSDOC. В дополнение к этому написали несколько типов (typings) в файлах .ts, чтобы ссылаться на них в аннотациях JSDOC.
Таким образом, опыт разработки в Visual Studio Code значительно вырос без особых усилий. Хотя здесь нет строгой проверки типов, редактирование кода так же удобно, как и в обычном проекте на TypeScript.
Объединив довольно скучную, но надежную технологию с новейшим компилятором TypeScript, мы смогли безопасно и легко добавлять новые функции и рефакторить код.
Документация и комментарии к коду
На первый взгляд, кодирование — это разговор между вами и компьютером: вы говорите компьютеру, что ему нужно сделать.
Но, на самом деле, кодирование — это разговор между вами и читателем кода. Хорошо известно, что код пишется один раз, а читается снова и снова. В первую очередь, вы пишете код для будущего себя.
В кодовую базу портала мы добавляли много комментариев, и почти все они успели доказать свою ценность за последние шесть лет. Очевидно, что код должен быть структурирован таким образом, чтобы помогать читателям понять его. Но я не верю в «самоописывающий» или «самодокументируемый» код.
До перехода на JSDOC, мы сделали удобные для чтения аннотации типов, задокументированные параметры функций и возвращаемые значения. Также мы задокументировали основные типы данных и сложные структуры вложенных объектов.
Эти комментарии оказались действительно полезны шесть лет спустя. Мы перевели их на машиночитаемый JSDOC и в объявления типов для компилятора TypeScript.
Вещи ломаются — всегда имейте под рукой набор тестов
В проекте есть только несколько автоматизированных юнит-тестов и более 50 (!) тестовых страниц, которые демонстрируют все страницы портала, компоненты, интерфейсы запроса данных, типы и настройки диаграмм. Они тестируют как оперативные, промежуточные, а также моковые (mock) данные.
Такие тестовые страницы нужны для того же, что и автоматизированные тесты: если мы исправляем ошибку, то сначала добавляем сценарий на соответствующую тестовую страницу. Если разрабатываем новую функцию, то одновременно создаем полную тестовую страницу.
Тестовая страница (test page)
Перед релизом мы проверяем все тестовые страницы вручную и сравниваем их с последним как визуально, так и функционально. Это занимает много времени, но позволяет быстро находить регрессии.
Я не думаю, что набор автоматизированных тестов будет более эффективным. Автоматически протестировать интерактивные визуализации данных в браузере практически невозможно. Визуальное регрессионное тестирование — ценный инструмент, но в нашем случае может дать слишком много ложных ошибок.
Обратная и прямая совместимость
В 2014 году наш портал должен был работать с Internet Explorer 9. Сейчас Internet Explorer не так важен, особенно при создании динамичного движка для построения графиков в браузере.
Однако мы решили сохранить совместимость со старыми браузерами. Портал данных — это международная платформа, куда заходят пользователи со всего мира. Не у всех есть последние модели компьютеров и новые браузеры.
Портал в браузере Internet Explorer 9
Мы смогли сохранить базовый уровень поддержки браузеров, используя скучные стандартные технологии. Конечно, есть и несколько современных веб-функций, но их мы активируем, только если поддерживает браузер, В этом нам помогает подход Progressive Enhancement (прогрессивное улучшение). Также мы используем Babel и polyfills, чтобы современные функции JavaScript работали и в старых браузерах.
Ваши абстракции могут и укусить
За все эти годы нас ограничивал не стек технологий. Скорее, на пути встали собственные абстракции.
Мы разделили пользовательский интерфейс на визуальные части (views) и создали базовый класс, аналогичный Backbone.View. (Сегодня все большие библиотеки JavaScript используют термин «component» вместо «view» для частей пользовательского интерфейса.) Для хранения данных и состояния мы использовали Backbone.Model. Это отлично сработало, но мы решили придерживаться собственных лучших практик.
Идея разделения Backbone model-view состоит в том, чтобы эта модель была единственным источником истины. DOM должен просто отражать данные модели. Все изменения должны исходить также из модели. Современные фреймворки, такие как React, Vue и Angular, соблюдают соглашение о том, что пользовательский интерфейс является «функцией состояния», то есть UI определенно является производным от состояния.
Мы нарушали этот принцип и иногда делали DOM источником истины. Это приводило к путанице с кодом, который рассматривал модель в качестве авторитетного источника.
Объектно-ориентированные диаграммы
Для графиков мы выбрали другой подход: создали классы диаграмм, отличающиеся от тех, которые описывали выше.
Сам D3 функционален. Диаграмма, как правило, создается и обновляется с помощью функции рендеринга, которая вызывает другие функции. Такие диаграммы являются вводными для этой большой функции. Больше состояния содержится в конкретных объектах.
Это делает D3 выразительным и гибким. Но код D3 трудно читать, так как есть небольшие соглашения (договоренности, которые не задокументированы) по работе со структурами диаграмм.
Ирен Рос и Майк Пенниси, разработчики из компании Bocoup, изобрели d3.chart — небольшую библиотеку поверх D3, которая представляет OOП на основе классов. Ее основной целью было структурировать и повторно использовать код построения диаграмм. Эти диаграммы состоят из слоев, каждый из которых отображает и обновляет определенную часть модели DOM с помощью D3. Кроме того, к диаграммам могут быть прикреплены другие диаграммы.
Общее правило OOП — «Композиция преобладает над наследованием». К сожалению, мы выбрали странное сочетание композиции и наследования для поведения диаграммы.
Нам нужно было использовать функции или простые классы вместо сложных иерархий классов. Люди до сих пор оборачивают D3 в OOП на основе классов, но ни одно решение на основе классов не смогло превзойти функциональную структуру D3.
Подведем итоги
С того момента, как мы разработали front-end часть портала данных в 2014 году, появилось много крутых подходов для создания веб-интерфейсов на основе JavaScript.
Сейчас UI-компоненты декларативны, можно обойтись без рендеринга HTML-шаблонов и обновления DOM вручную. Вы просто обновляете состояние, и фреймворк обновляет DOM. Этот однонаправленный поток данных устраняет целый класс багов.
Технологии, которые мы выбрали в 2014 году, либо выдержали испытание временем, либо были позволяли легко перейти на другой стек. Можно подумать, что нам повезло, но мы осознанно сделали выбор в пользу долговечных технологий.
В компании 9elements мы всегда стараемся использовать современные технологии, в том числе оценивать экспериментальные front-end технологии, которые могут быть нерелевантны через 3-4 года. К сожалению, многие open-source JavaScript-проекты могут быть прогрессивными с технической точки зрения, но при этом оказаться нестабильными.
Для каждого проекта мы ищем оптимальный баланс между устойчивыми технологиями с минимальными рисками и инновационным стеком, что помогает нам создавать качественный продукт.