Как перенести на TypeScript большую кодовую базу React UI-компонентов

5270a6d98436e018bcee00da893f8c87

Привет! Меня зовут Иван Греков, я работаю UI-разработчиком в frontend-команде Badoo. Главные задачи нашей команды — создание новых и поддержка существующих пользовательских интерфейсов для сайтов и приложений Badoo и Bumble. 

Когда в конце 2019 года мы начали параллельно работать над несколькими проектами, мы задумались над повышением стабильности кода и возможностью его многократного использования. Для этого мы решили переписать наши 630 React UI-компонентов на TypeScript. Я расскажу о том, как мы работали над ними без перерыва в доставке фич и как организовали поэтапный переход на TypeScript для UI-разработчиков, которым этот язык был в новинку.

Почему мы решились на переход

К началу проекта мы уже отделили бизнес-логику от UI-компонентов: это очень помогло нам при поддержке кода приложений. Когда мы начали создавать общие модули с бизнес-логикой, JS-разработчики из команды фронтенда сразу писали их на TypeScript, чтобы обеспечить типобезопасность (type safety). 

Наша UI-команда тоже решила применить этот подход, так как хорошо понимала его преимущества.

  • Типобезопасность наших UI-компонентов.Качество продакшен-кода улучшается, если мы проверяем типы при сборке, а не в среде выполнения. В большинстве наших приложений и проектов на основе React уже использовались prop-types. Но главная проблема заключалась в том, что проверки типов в prop-types не выполнялись принудительно при сборке и упаковке приложений. И если кто-то менял API или тип одного из компонентов, это могло оказаться критическим изменением и привести к рассинхронизации типов компонентов на разных уровнях с возникновением багов или предупреждений.

  • Единство кодовой базы на TypeScript. Благодаря этому на написание и проверку кода тратится меньше времени: после создания компонента можно его многократно использовать в разных проектах.

Главными целями нашего проекта стали более высокая надёжность приложений и предсказуемость процесса написания кода.

С чего мы начинали

Наша кодовая база лежит в основе нескольких отдельных проектов. Для всех своих веб-приложений мы используем единые соглашения и требования к качеству кода. Фронтенд приложений создан в React.js. 

Мы разделили бизнес-логику и UI-представление в соответствии с архитектурным подходом, принятым нами для фронтенда. У нас есть два типа компонентов:

  • контейнеры, которые представляют бизнес-логику на JavaScript;

  • UI-компоненты, написанные как stateless функции. 

Это помогает нам писать и рефакторить UI-компоненты, не трогая бизнес-логику, и заодно даёт прекрасную возможность экспериментировать и сопровождать код без помех разработке фич.

В нашей UI-команде пять разработчиков, которые специализируются на создании пользовательских интерфейсов. Все они, включая меня, опытные фронтендеры, умеющие работать с JavaScript и React.js. Однако многие из нас до этого либо не работали с TypeScript, либо только начинали с ним знакомиться. Поэтому мы поставили себе ещё одну цель: помочь коллегам набраться опыта и привести минимальный уровень владения TypeScript в команде к единому знаменателю.

Для этого нам было необходимо организовать коммуникацию между всеми участниками проекта. Это было одной из моих главных задач: я выступил координатором. Чтобы сохранять прозрачность и чёткость ожиданий и конечных результатов, я регулярно отчитывался перед руководством о текущем состоянии дел.

Мы сосредоточились на нескольких ключевых направлениях: коде, процессе и команде. Когда я планировал проект и размышлял о том, как организовать работу по всем направлениям, я нашёл подходящую управленческую стратегию — PDCA (Plan-Do-Сheck-Act). Это алгоритм принятия решений: сначала планирование, потом действие, затем проверка и корректировка действия. По этой схеме я и начал действовать: оценил состояние кодовой базы, затем спланировал работу, оценивал прогресс и подводил итоги.

Состояние кодовой базы

Перед запуском масштабного проекта нам важно было понимать, с чем придётся работать. Поэтому мы начали с вычисления качественных и количественных индексов и метрик.

Объём кода

Чтобы быстро оценить объём будущих работ, можно посчитать количество компонентов, которые у нас уже есть. Для этого воспользуемся утилитой cloc:

f41f98b261e5331f92dc243a06250fb2

В середине января в кодовой базе было 554 JSX-файла, из которых 227 — компоненты и 227 — относящиеся к компонентам файлы спецификаций с VRT-тестами. На тот момент мы перевели на TypeScript 62 компонента и 62 теста.

Эти данные — показатель прозрачности работы команды: можно каждую неделю измерять, как продвигается проект, и сравнивать прогресс с нашими ожиданиями. Мы назначили оценку на середину пятницы, потому что в другие дни у нас обычно бывает по два релиза, а в пятницу — только один, утром. Так что в этот день мы могли оценить объём переведённых и отправленных в эксплуатацию компонентов. 

Это позволяет увидеть, не буксует ли команда и не слишком ли мы отвлекались на другие проекты. Также мы могли регулярно давать обратную связь друг другу и сообщать руководству о текущей ситуации, чтобы не было несбыточных ожиданий.

Покрытие тестами

Покрытие — ключевой показатель для оценки изменений в кодовой базе. Любой код мигрировать легче, если написаны тесты. Поэтому на начальном этапе планирования самые важные вопросы — есть ли у нас тесты для компонентов и какая доля компонентов покрыта тестами?  

В подобных проектах особенно полезны визуальные регрессионные тесты. Они помогают сосредоточиться на функциональности тестирования, а благодаря стабильности покрытия мы тратим на визуальные тесты меньше ресурсов. Например, если у компонента три разных состояния, то мы покрываем их тремя отдельными тестами. Так, на начало проекта у нас было около 2000 тестов.

Планирование

Когда мы предварительно оценили объём работ, взялись за распределение задач между участниками команды. Одним из важных условий было не останавливать разработку новой функциональности, поэтому мы хотели сбалансировать нагрузку разработчиков. Для этого мы определили последовательность задач, зависимости между ними и их сложность. 

Начнём с анализа кода.

Анализ графа зависимостей

Если нам известен размер кодовой базы, мы можем приблизительно оценить сложность работы. Но это лишь небольшая часть процесса. Давайте подробнее поговорим о сложности.

Прежде чем начинать работу с кодом, нужно выявить его внутренние взаимосвязи. Нужно понять, какие компоненты созависимы, и в зависимости от этого установить порядок переработки компонентов. Для этого мы построили граф зависимостей с помощью инструмента madge, который строит графы на основе структуры проекта. Для нас было важно, что madge умеет строить и на основе webpack-конфигурации. У этого инструмента много настроек, помогающих создавать правильные схемы.

50c3752e9b5a226f5b121318e9c67bb0

Построив граф, мы начали переводить на TypeScript компоненты, которые расположены в правой части: у них нет или очень мало зависимостей.

Такой подход помог нам решать трудности, связанные со сложностью типов компонентов, и позволил распределить работу между участниками команды на основе нескольких интерфейсов компонентов без проблем с перекрытием компонентов или типов. 

Мы каждую неделю обновляли граф и пересматривали очередь на перевод в TypeScript. Каждую пятницу оставалось всё меньше зависимостей и компонентов. Граф был хорошей иллюстрацией прогресса и источником данных для принятия решений, если мы начинали буксовать.

Оценка сложности компонентов

Допустим, мы составили список компонентов на перевод и приготовили для них задачи. Также на основе графа зависимостей мы решили, в каком порядке будем их переводить. Но как нам максимально эффективно распределить задачи между участниками команды, которые знакомы с TypeScript на разном уровне?

Для этого мы пометили компоненты тегами. Использовали следующие теги соглашений и фильтрации:

  • TS basic — для компонентов с простым и понятным интерфейсом, а также с отсутствием или минимальным количеством зависимостей;

  • TS component — для средних компонентов, у которых достаточно понятный интерфейс и минимальное количество зависимостей;

  • TS view — для сложных компонентов с составным интерфейсом и значительным количеством зависимостей.

С учётом уровня владения TypeScript и своего прогресса в обучении разработчики начинают с базовых компонентов и постепенно переходят к более сложным. Мы договорились, что сначала я буду ставить задачи разработчикам. А по мере того как они будут осваиваться, они будут становиться более независимыми. В конечном итоге коллеги начали работать с задачами самостоятельно.

Планируя работу, нужно держать в голове дополнительные вопросы о сложности компонентов. Можно ли перевести конкретный компонент автоматически? Хранит ли он состояние? Есть ли в его типе перечисление (Enum) или объединение (Union)? Нужно ли поменять его тип или написать новый?

Разбивка проекта на этапы

Учитывая сложность и масштаб проекта, а также размер команды, вы можете разделить проект на фрагменты с разным количеством компонентов. Мне было удобно каждую неделю оценивать прогресс на основе релизных циклов. Но это можно делать и раз в две недели, и раз в месяц. Я предпочитал использовать диаграммы Ганта и регулярно отслеживать, сколько времени потрачено на проект: это помогало сохранять высокий уровень прозрачности и качественно отчитываться перед руководством.

a56c4bb419e3d6c17f5103d7be6e0c89

Но прежде чем переходить к программированию, нужно было пройти несколько первичных этапов.

Этап 0 — создание базы знаний команды

Некоторые решения помогли нашей команде сэкономить время. Одним из таких было решение синхронизировать работу, и мы добились этого, делясь знаниями друг с другом. Здесь важно уточнить, что каждый участник команды либо уже владел TypeScript, либо изучал его. Как правило, для начала знакомства мы давали список для чтения о TypeScript, а уже после этого — простые задачи для начинающих. Параллельно с этим разработчики, которые уже были знакомы с TypeScript, получали новый опыт, решая более сложные задачи. 

Важно было организовать обмен опытом между обеими категориями разработчиков. Хорошим решением стало создание единого документа, в котором описывались принятые в команде соглашения по стилям кода. Во-первых, это сэкономило нам время, потому что каждый из нас в поисках ответа на свой вопрос мог обратиться к хранилищу знаний, а не искать информацию в интернете. Во-вторых, в процессе работы командные соглашения могут меняться (это нормально) и важно открыто это обсуждать и транслировать, в чём очень помогает единый источник актуальных данных. 

Мы добавили в базу знаний рекомендации и информацию по разным темам:

  • конфликты типов с инлайненными CSS-стилями, если они определены как отдельные константы или объекты;

  • использование глобальных компонентов, которые не импортируются и не используются в свойствах внутри компонентов;

  • использование нескольких Enums в одном с набором ключей для первых значений во всех Enums;

  • использование объединения для массива из заранее определённых массивов значений.

Один из главных пунктов — соглашения по стилю кода. Перед началом работы мы унифицировали их во всех проектах и приняли единый набор инструментов для JS-файлов: ESLint и Prettier. До этого мы могли писать разный код, но теперь мы привели его к единому стилю. Это очень помогло при переходе на TypeScript. А поскольку стиль автоматически проверялся перед коммитами, это не увеличивало сложность и не требовало от разработчиков больше времени.

Этап 1 — подготовка обучения

Мы предоставили всем участникам список вводных материалов для ознакомления. После того как люди в удобном для них темпе (параллельно с работой над другими проектами) прошли вводную часть, они получили свои первые задачи. Мы начали с TypeScript-компонентов, помеченных тегом basic.

Поскольку при анализе задач разработчики уже знали о соглашениях по написанию кода, зафиксированных в нашей базе знаний, процесс шёл быстро. Более того, благодаря проверкам на основные ошибки в процессе компиляции TypeScript, а также благодаря VRT-тестированию в ходе анализа разработчики могли спокойно учиться и решать задачи с меньшими рисками. VRT не только помогает поддерживать качество кода, но и уменьшает уровень стресса при изучении новых технологий, экономит время на тестировании визуальных изменений.

Совет: делегируйте задачи, отслеживайте прогресс

Как я уже упоминал, отслеживание прогресса с помощью инструментов и диаграмм Ганта поможет вам увидеть, как масштабировать проект силами команды. Возможно, вам потребуется делегировать техническую часть работы и пригласить больше разработчиков, если это позволит ускорить завершение проекта. Помните об этом, когда будете начинать проект. 

А теперь пора переходить к коду.

Оценка прогресса

На этом этапе проекта нужно отслеживать основные показатели, касающиеся прогресса в работе всей команды.

Используйте измеримые показатели

Это очевидно, но напомню, что качество коммуникации зависит от прозрачности процесса реализации проекта. А её можно достичь, оперируя показателями, которые понятны и участникам команды, и руководству. В нашем проекте мы использовали такие метрики:

  • покрытие кодовой базы в продакшене — доля кода на TypeScript;

  • вовлечённость UI-разработчиков — сколько участников команды знакомы с технологией и используют её;

  • темп работы — сколько задач разработчики выпустили за период.

Коммуникация внутри команды

Мы не проводили специальных встреч по этому проекту, а обсуждали прогресс на еженедельных ретроспективных встречах подразделения. За час мы рассматривали текущую ситуацию в разрезе зон ответственности каждого участника команды. Я рассказывал о прогрессе проекта в целом и спрашивал, есть ли какие-то проблемы, которые нужно решить. Чаще всего о проблемах становилось известно до встречи, а их решения документировались в командных соглашениях. Также я каждую неделю сообщал о прогрессе руководству департамента и отвечал на вопросы по проекту.

Обновляйте соглашения

Во время работы над проектом мы находили старые паттерны и тупиковые ситуации и, чтобы эффективно с ними разбираться, документировали командные соглашения. Это важная часть процесса — рассказывать команде об изменениях, как только они возникают.

Регулярные отчёты

Управляя проектом, важно постоянно сравнивать показатели и держать руку на пульсе.

Я уже упоминал инструменты madge и cloc, с помощью которых мы проводили первичную оценку кодовой базы. Но ещё с их помощью я еженедельно собирал данные, чтобы оценить, как продвигается работа. Мы видели, насколько быстро движется команда и каждый из нас в отдельности. Иногда важно остановиться и подумать: как я могу повлиять на показатели? всё ли хорошо или нужно ускориться? как нам это сделать? Ответы на эти вопросы помогали мне сохранять прозрачность работы над проектом.

Результаты

Мы закончили работу примерно через три месяца. В результате все наши разработчики освоили TypeScript и теперь пишут на нём новые компоненты.

Благодаря распределению задач мы смогли выделить для команды время между написанием нового кода и переписыванием старого. Еженедельно мы перебалансировали набор задач для каждого разработчика.

Для нас успешное завершение этого проекта стало важным результатом, потому что главные сомнения были связаны с переводом кодовой базы с JavaScript на TypeScript и его влиянием на доставку продукта пользователям. Процесс разработки не остановился, что помогло повысить прозрачность перехода.

Команда успешно освоила язык

Одними из главных задач были освоение TypeScript командой и создание общей базы знаний об этой технологии. Теперь уровень навыков внутри команды сравнялся и все участники пишут на TypeScript.

Полностью прозрачный процесс

Каждую неделю мы обсуждали прогресс внутри команды и с руководством. Нам очень помогли упомянутые выше процедуры взаимодействия, призванные помочь распространению знаний. Мы не тратили много времени на сбор данных для отчётности, потому что это было по большей части автоматизировано.

Более надёжная и быстрая разработка

Поскольку теперь мы вылавливаем определённые ошибки на стадии сборки, а не на стадии QA и не в проде, процесс разработки изменился в лучшую сторону. А благодаря использованию Prettier и ESLint перед коммитом разработчики просто не могут совершать крупные распространённые ошибки, которые приводят к отказу функциональности приложений.

Совет: выбирайте правильные инструменты

Каждый разработчик вправе сам выбирать набор инструментов для работы над проектом. Однако хочу дать совет: выбирайте транспиляторы, кодмоды и сниппеты. Например, расширение VSCode компании Lyft позволяет в один клик переименовывать файлы и переносить в TypeScript структуры и типы React-компонентов. Оно поддерживает React начиная с версии 16.3.14 и до 16.4.0, его можно настроить таким образом, чтобы оно поддерживало и более новый синтаксис. Хотя на момент написания статьи расширение находится в архиве, оно прекрасно выполняет свои функции.

Также вы можете использовать специальные codemod«ы для перевода кода в TypeScript, доступные на GitHub и npm. Если они позволяют перевести компоненты без ухудшения качества кода, лучше воспользоваться ими и потратить сэкономленное время на другую работу.

Этот совет применим к любому масштабному процессу миграции в сфере фронтенда.

Список свежих статей о React и TypeScript

Также хочу поделиться ссылкам на материалы, которые освещают разные аспекты переноса React UI-компонентов на TypeScript. Пока я работал над этой статьёй, в первое полугодие вышло много отличных материалов на английском языке (и не только). Я разделил ссылки на группы в зависимости от того, на каком уровне вы уже владеете TypeScript.

О выборе между JavaScript и TypeScript:

  1. Choosing Between TypeScript vs JavaScript: Technology, Popularity

  2. How TypeScript is making programming better

  3. The TypeScript Tax

О том, как настроить TypeScript:

  1. How does TypeScript work? The bird«s eye view

  2. Creating web apps via TypeScript and webpack

  3. Set Up a Typescript React Redux Project

  4. React TypeScript: Basics and Best Practices

  5. Setting TypeScript For Modern React Projects Using Webpack

  6. Setting up efficient workflows with ESLint, Prettier and TypeScript

  7. The Practical Guide to Start React Testing Library with TypeScript.

О переводе на TypeScript имеющейся кодовой базы React:

  1. How to move your project to TypeScript — at your own pace

  2. How to Easily Migrate From JavaScript to TypeScript

  3. Gradually using TypeScript in Your React Project

  4. Porting to TypeScript Solved Our API Woes

  5. How to Migrate a React App to TypeScript

  6. Convert Your Javascript React App to TypeScript, the Easy Guide

  7. TypeScript and React

  8. Using TypeScript with React

  9. The Great CoffeeScript to Typescript Migration of 2017

  10. 12 советов по внедрению TypeScript в React-приложениях

О том, как писать на TypeScript новую кодовую базу React:

  1. Build a Tic Tac Toe App with TypeScript, React and Mocha

  2. A Practical Guide to TypeScript — How to Build a Pokedex App Using HTML, CSS, and TypeScript

  3. Create a React component library with TypeScript and Storybook

  4. How To Build a Customer List Management App with React and TypeScript

  5. Using TypeScript in Grommet Applications

Для более глубокого изучения TypeScript:

  1. TypeScript Best Practices — Useless Interfaces, Classes, and Promises

  2. React with TypeScript: Best Practices

  3. Great import schism: Typescript confusion around imports explained

  4. Разница между типом и интерфейсом в TypeScript

© Habrahabr.ru