Connect with us

Разработка

Рефакторинг кодовой базы в Slack: Стабилизация, Модуляризация и Модернизация

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

Опубликовано

/

     
     

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

Я резюмирую свои выводы в этой статье.

Зачем нужно было серьезное улучшение кодовой базы?

Код писался с 2015 года на Android и с 2013 года на iOS. На момент, когда команда занялась улучшением кода (в 2020 году), ему исполнилось 5 и 7 лет соответственно. Капитальная переделка была необходима, потому что:

  • код в основном оставался монолитным, а набор функций рос экспоненциально, как и команда, а методы работы не масштабировались.
  • код имел широко распространенную несогласованность и устаревшие шаблоны с отсутствием разделения проблем.
  • появились новые технологии как для Android (Kotlin, корутины и т.д.), так и для iOS (Swift).
  • было множество миграций, на которых терялась скорость киосков (например, с Obj-C на Swift, AutoValueGson на Android вместо Kotlin Moshi)
  • сложность влияла на дорожную карту продукта
  • команда увидела значительные улучшения в своем веб-приложении после его переработки

Что можно было сделать

  • Полностью переписать? Это слишком рискованно, так как многие основные функции все еще не были затронуты.
  • Использовать кроссплатформенный подход (React Native, Flutter)? Пробовали раньше и сталкивались с различными проблемами, например, производительностью.
  • Решил использовать многоэтапный подход к рефакторингу — стабилизировать, выделить модули и модернизировать.

Как это было спланировано

Чтобы все это заработало, нужно было не просто начать. Нужно было предпринять шаги, чтобы спланировать все:

  1. Они назвали проект Project Duplo. Именование важно, чтобы проект можно было легко идентифицировать.
  2. Начали со списка намеченных целей. Нужно знать, какова цель проекта.
  3. Некоторые отдельные участники возглавили инициативы по раннему изучению возможностей и составлению более подробных планов.
  4. Поделились этим с заинтересованными сторонами, которые станут все реализовывать, учитывая, что это потребует времени и инвестиций (компромисса с некоторыми другими вещами). Сообщили им, что они получат взамен.
  5. Четко расписали, что нужно решить, как измерить прогресс и каков риск с планом смягчения последствий.

Чего они хотели достичь

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

Фаза стабилизации

Избавление от наихудшего технического долга и завершение важных миграций. Общая продолжительность — 6 месяцев.

iOS

  • Перенос оставшегося кода Obj-C на Swift, чтобы обеспечить более согласованную кодовую базу. Это также позволило им позже использовать такие функции, как, например, Combine.
  • Завершение миграции библиотек доступа к данным — чтобы обернуть CoreData и избежать прямого доступа к ней.
  • Завершение миграции на нативную сетевую библиотеку — чтобы в кодовой базе не использовался другой подход (сторонняя библиотека) и чтобы сделать все более согласованным.

Android

  • Разделение монолитного API сетевых вызовов по функциональным областям — это позволило выделить интерфейсы для нужд отдельных функций.
  • Разделение монолитного интерфейса доступа к базе данных на несколько объектов доступа к данным (DAO).
  • Перенос оставшихся запросов к базе данных в SQLDelight — это также помогло сделать все более согласованным.
  • Стандартизация шаблона Репозиторий для доступа к сетевым/дисковым данным — это также сделает вещи более согласованными.
  • Удаление оставшегося использования шины событий Otto — замена его на RxJava, который лучше работает.

Метрики

  • Отображение процента устаревшего кода в базе с течением времени
  • Отображение количества строк Obj-C кода с течением времени

Фаза модуляризации

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

Очевидно, что это самая важная часть улучшения кодовой базы для ускорения разработки, как написала команда Slack:

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

Весь этап Модульности совместно с Модернизацией занял в общей сложности год.

Преимущества модульного подхода

  • Более быстрое время компиляции как для локальной версии, так и для непрерывной интеграции.
  • Разработчики могут самостоятельно создавать и тестировать свой код.
  • Меньшая кодовая база для работы, повышение скорости доставки
  • Более четкое владение и разделение ответственности.
  • Сведен к минимуму бесхозный код и распутаны зависимости.
  • Легче отслеживать ход работы над каждой фияей.

Модульный подход

Разбит на 3 разные категории

  • Модули функций — это функции, ориентированные на пользователя (связанные с пользовательским интерфейсом). Интерфейс и реализация разделены для упрощения связи между функциями.
  • Сервисные модули — это обычная логика, не связанная с пользовательским интерфейсом. Интерфейс и реализация разделены для упрощения связи между службами.
  • Модули библиотеки — это структура данных и утилиты. Реализация и интерфейсы здесь общие, следовательно, между библиотечными модулями нет ссылок.

Инструменты

Для успешной модуляризации более важными являются инструменты. Привлекли Команду опыта разработчиков (Developer Experience Team) для поддержки необходимого тулинга.

  • iOS перешел с инструмента Xcode CI на использование Bazel
  • Android использует Gradle и функцию кэширования конфигурации в стадии разработки
  • Появился сценария генерации кода для создания шаблонов новых модулей, создания моделей CoreData и флагов функций
  • Начали использовать функцию Anvil от Square, чтобы улучшить производительность Dagger DI благодаря умному использованию Anvil плагинов компилятора Kotlin
  • Провели миграция с KAPT на KSP, где Anvil также помог снизить потребность в KAPT. Подробнее об использовании Anvil здесь.
  • Удалили AutoValue и AutoValueGson в пользу классов данных Kotlin, Moshi и Moshi-IR (экспериментально).
  • Начали использовали Gradle Enterprise не только в качестве распределенного кэша сборки, но и для проверки того, где разработчики сталкиваются с задержками.

Фаза модернизации

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

Совместно с модульностью процесс занял 1 год.

Модернизация iOS

  • Переход от функциональной архитектуры MVVM+C к проприетарной VIPER — использование Swift с дженериками и шаблонами для создания базовой реализации.
  • Наложение более строгого линтинга поверх уже работающего Swiftlint при каждом коммите, чтобы гарантировать отсутствие анти-шаблонов, чтобы избегать глобальных синглетонов, использовать Combine и использовать проприетарный SlackKit вместо компонентов UIKit.
  • Обеспечение внедрения реактивной структуры Apple Combine вместо других, например, RxSwift, что готовит команду к будущему внедрению SwiftUI.
  • Расширение использования SlackKit для компонентов пользовательского интерфейса вместо использования нативных компонентов пользовательского интерфейса iOS (на данный момент UIKit и SwiftUI в будущем).

Модернизация Android

  • Замена Gson на Moshi, переход к сериализации Kotlin.
  • Замена Android Priority Job Queue на WorkManager из-за устаревания APJQ.
  • Замена сетевых вызовов API внутренним проектом под названием Guinness, созданным с использованием Retrofit, Moshi, Okio и OkHttp. Исходный код большей части фрагментов Guinness находится в открытом доступе под именем EitherNet.
  • Внедрение корутин в качестве еще одного варианта RxJava, но без принуждения к их глобальному внедрению.
  • Эксперименты с Jetpack Compose, так как он все еще стабилизируется. Поверьте, он будет определять будущее.

Результаты, достижения

В целом команда добилась того, к чему стремилась, за 1.5 года. Хотя проект еще не считается завершенным (и я думаю, что никогда не будет завершенным), он многого достиг:

  • модульность кодовой базы достигает 81% для iOS и 92% для Android, модернизирована большая часть кодовой базы — 68% для iOS
  • добавлено больше модулей — 280 для iOS и 330 для Android
  • улучшено время CI для тестов слияния, сокращено время тестирования на 63% для iOS и на 30% для Android
  • развертывание прототипирования на мобильных устройствах происходит быстрее
  • улучшение общего настроения разработчиков

Появившиеся проблемы и что нужно решить

Несмотря на множество преимуществ, есть некоторые проблемы.

Более длительное время локальной сборки

Это привело к увеличению времени локальной сборки из-за Bazel. Однако этому противодействуют с помощью

  • Лучший машин M1 MacBook Pro
  • Продолжающихся улучшений развертывания Bazel

Сложные отношения между модулями

С модулями «Фичи», «Сервисы» и «Библиотеки» все еще недостаточно проработано, поскольку Фичи зависят от Фич, а Сервисы зависят от Сервисов, что может привести к более глубоким иерархическим и циклическим отношениям. Требуется дополнительная доработка.

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

Задача внедрения зависимостей

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

С модульностью это теперь обрабатывается с помощью стандартных инъекций инициализаторов, которые:

  • Проще использовать и которые знакомы разработчикам
  • Есть гарантированная доступность зависимостей во время компиляции
  • Они избегают глобального состояния или синглтонов

Кроме того, разработчики изучают Needle (Dagger для Swift) в качестве платформы зависимостей, чтобы модули могли явно определять свои зависимости и создавать их без необходимости связывать все реализации в целевом приложении.

Нужно больше улучшений в кодовой базе Android

  • Продолжается миграция на Kotlin, в настоящее время 92% кодовой базы на нем
  • Большее принятие Jetpack Compose по мере улучшения производительности
  • Принятие корутин Kotlin для асинхронной обработки

Мой личный взгляд

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

Что мне действительно нравится в инициативе:

  1. Разделение монолита на модульные части позволяет сократить время сборки, улучшить владение, и каждая команда может работать независимо.
  2. Нацеливание на модернизацию кодовой базы с помощью новейшего стека технологий, например, перенос Obj-C на Swift и Java на Kotlin, использование Combine и корутин и т.д.
  3. Целенаправленное участие некоторых членов команды, а также вклад всех разработчиков в инициативу с помощью команды Developer Experience.
  4. Различные измеримые показатели результативности, например, улучшение времени CI, % принятия, настроение разработчиков и т.д.

Вещи, которые, по моему мнению, могли бы быть лучше (с оговоркой, что это всего лишь моя личная ограниченная точка зрения):

  1. Миграция выполняется слишком рано, до того, как SwiftUI и Jetpack Compose будут признаны полезными для команды. В ближайшие 2–3 года мобильный мир будет полон SwiftUI и Jetpack Compose, поэтому команда Slack, планирующая, что инициатива Duplo продлится 5 лет, может и ошибиться. Возможно, с помощью SlackKit они смогут скрыть влияние этих изменений.
  2. Сотни модулей и многоуровневая модульная структура могут усложнить ситуацию в будущем. Для обеспечения масштабируемости в долгосрочной перспективе может потребоваться более точное разделение модулей, позволяющее избежать непрерывного роста их количества.
  3. Инициатива iOS и Android выполняется относительно по-разному, хотя фазы высокого уровня (стабилизация, модульность и модернизация) считаются одинаковыми. Возможно, на этапе модульности могут быть общие библиотеки или службы, которые можно использовать на обеих платформах.
  4. Наиболее предпочтительное согласование шаблонов между командами может ограничить модульную команду в большей автономии, в изучении новых шаблонов и использовании шаблонов, которые лучше подходят для конкретного функционального модуля. Принудительное выравнивание — это балансировка, когда чрезмерное выравнивание может задушить эволюционное развитие базы кода.

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

Источник

Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.
Advertisement

Популярное

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: