Привет! Меня зовут Карина. Я соосновательница приложения Drum Pads 24 и компании Beat Squad. Мы делаем мобильные приложения для создания музыки и работы с аудио. У нас более 75 миллионов установок приложений. Я отвечаю за техническую часть всех проектов. Стремлюсь к балансу между качеством и скоростью. Люблю изучать практики команд разработки разного размера и внедрять подходящие нам в наши процессы. 

7 лет назад на одной ИТ-конференции спикер рассказывал про новую возможность Android платформы. После доклада его спросили про архитектуру, которую они используют в своей команде для разработки Android приложений. Его ответ заключался в том, что архитектура не важна, что каждый разработчик может делать так, как он хочет. Он сказал, что главное - это не где какие файлы лежат и что в них написано, а главное, чтобы были новые фичи. В наших приложениях в то время никакой архитектуры не было и мы только начинали интересоваться этим вопросом, но ответ спикера меня удивил. Уже тогда мне казалось, что такой подход только усложняет работу, а не упрощает. Например, когда программист работает над новой фичой, ему приходится проанализировать несколько вариантов, сделанных разными людьми в рамках одного проекта, и потратить время на принятие решения о том, какой же из этих вариантов использовать сейчас. 

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

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

Наше первое приложение Drum Pads 24 появилось в 2013 году. В то время мы не использовали никакие архитектурные подходы. Позже я познакомилась с Clean Architecture и мы начали использовать эти принципы как в iOS, так и в Android приложениях. И если наши новые приложения разрабатываются уже с учетом этих принципов, то старые приложения требуют рефакторинга. Поэтому мне всегда интересен опыт других команд в этом вопросе. На недавно прошедшей онлайн-конференции App Excelence от Google разработчики из Headspace (Грег Реми) и Duolingo (Леон Рабинович) обсуждали, почему они делали рефакторинг и каковы были их результаты. Хочу поделиться заметками с этой конференции и немного рассказать о нашем опыте.

Когда нужен рефакторинг?

Обе команды - и Headspace, и Duolingo, - использовали MVVM паттерн и рекомендации от Google. Кстати, Google в конце 2021 года выпустила обновленный гайд по архитектуре и теперь в нем упоминается UDF (unidirectional data flow) паттерн вместо MVVM.

Грег и Леон назвали причины, которые привели их к рефакторингу. Среди них - большие монолитные классы и сильная связность между ними. Это приводило к разным проблемам. Например, к сложностям во внедрении новых фичей и к тому, что новые фичи разрабатывались значительно дольше, чем изначально оценивалось. Другая проблема - изменения в одном месте приводили к неожиданным неприятным последствиям и багам в другом месте. В обоих приложениях было много крашей и много жалоб от пользователей. Для Duolingo также было важно улучшить плавность анимаций и улучшить производительность для слабых бюджетных девайсов, которые используют их пользователи на рынках из развивающихся стран.

Переписывать с нуля или обновлять существующее приложение?

В команде Headspace на момент старта рефакторинга было 6 Android разработчиков. Они решили переписать приложение с нуля, решив, что это займет меньше времени. Одновременно развивать старое приложение и писать новое с нуля было бы сложно. Поэтому команда приняла решение поддерживать существующее приложение только для исправления критичных багов, а основные силы направить на разработку нового приложения. Команда выделила ключевые функции. Каждый разработчик выбрал одну функцию и в течение недели создал прототип для ее реализации. Результаты работы каждый презентовал всей команде. Это позволило им быстро познакомиться с достоинствами и недостатками архитектуры, с которыми каждый из них столкнулся. После того, как была готова базовая версия приложения на основе созданных прототипов, разработчики реализовали остальные функции приложения уже с использованием новой архитектуры.

Duolingo же использовали итеративный подход к обновлению приложения, не переписывая его с нуля. В их команде было 40 разработчиков. Но для многих из них эта работа или работа с Android платформой была впервые. Поэтому небольшая команда опытных разработчиков начала работу над прототипом с новой архитектурой и документацией. Позже остальные разработчики смогли использовать этот прототип и документацию для обучения, что позволило им легко подключиться к процессу рефакторинга. Такой подход позволил переписать основную часть приложения за 2 месяца. Но работы по рефакторингу не остановились на этом. Так, в приложении Duolingo еще не везде используются корутины и не полностью внедрен HILT.

Выбор архитектуры

В Headspace до рефакторинга использовали MVP и уже был опыт использования MVVM. Одним из недостатков MVP Грег назвал необходимость написания логики для управления жизненным циклом view. В их приложении такая логика была ответственностью презентера, который отвечал, например, за освобождение ресурсов. Это приводило к созданию громоздких и запутанных классов. Для рефакторинга рассматривался MVI, а также VIPER, который использовала их iOS команда. Но из-за небольшого размера Android команды они решили выбрать MVVM. Он позволял разработчикам сфокусироваться на бизнес-фичах, а не на технических деталях, таких, как управление жизненным циклом view. Эту задачу взяли на себя Android компоненты. Использование Jetpack библиотеки можно сравнить с тем, как будто большая команда опытных разработчиков работает над улучшением твоего кода, а ты можешь сфокусироваться на ключевых бизнес задачах и разрабатывать продукт быстрее.

Команда Duolingo разработала прототипы с использованием трех разных подходов - MVP, MVI и MVVM, чтобы сравнить достоинства и недостатки каждого из них. MVP быстро оказался в числе аутсайдеров, так как одним из критериев для Duolingo была реактивность. Между MVI и MVVM они выбрали MVVM. Одним из его преимуществ Леон назвал легкость разделения ответственности между классами, что значительно упрощало принятие решения о том, где какая логика должны быть расположена. Это, в свою очередь, упрощало тестирование и планирование дальнейших изменений. Леон отметил пользу HILT, который помог их большой команде изолированно разрабатывать фичи. Это позволило им решить проблему, которая часто возникала раньше: когда изменение взаимодействия с одним объектом приводило к багам в других точках приложения, при этом зависимость этих частей приложения была неочевидной. 

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

Как упростить рефакторинг?

В процессе рефакторинга команды Headspace и Duolingo использовали разные инженерные практики. Среди них - ревью кода, которое позволяло разработчикам обмениваться опытом. Важной частью было создание документов, описывающих лучшие практики. Такие документы становились составляющей процесса онбординга новых участников команды. Headspace также использовали создание текстовых спецификаций для проектирования частей приложения и обсуждали их на митингах, чтобы придти к наилучшему возможному решению. Обе команды использовали автоматизацию проверок, где это было возможно, и явное обозначали устаревшие паттерны и подходы, чтобы разработчики не копировали такие решения в новом коде. Регулярные обсуждения и актуализация принятых подходов вместе с командой позволяли каждому видеть ценность и пользу от единообразия кодовой базы. 

Результаты рефакторинга

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

Разработчики стали меньше переживать о том, что изменения в одном месте сломают что-то неожиданное или ухудшат производительность в другом месте. Такая проблема часто возникала у Duolingo из-за использования централизованного DuoState объекта. Репозитории и ViewModel’и позволили изолировать такие проблемы, связанные с влиянием кода на другие части приложения, и в целом значительно сократить их. У Headspace процент покрытия тестами повысился до 80%. В таких условиях программистам было гораздо приятнее и спокойнее работать с кодом. Удовольствие и удовлетворённость команды - важный компонент для метрики “счастье разработчика”.

Обе команды благодаря рефакторингу смогли значительно уменьшить количество багов. Команде Duolingo удалось снизить ANR на 41%. Headspасe подняли среднюю оценку приложения с ~3.5 до 4.8, как за счет уменьшения числа багов, так и благодаря использованию библиотеки App Review. Высокая оценка позволила приложению улучшить позиции в Google Play. Это привело к росту платных подписчиков на 20% и увеличению среднего количества пользователей в месяц (MAU) на 15%.

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

Для оптимизации кода в процессе разработки Duolingo использовали бенчмаркинг Android Studio и Perfetto. Дополнительно они внедряли внутренние флаги, которые позволяли отслеживать потоки данных внутри приложения и быстрее решать возникающие проблемы. Когда билд уже попадал на устройства пользователей, то, помимо анализа крашей через Firebase Crashlytics, команде помогало наблюдение за всеми ключевыми показателями приложения, включая время запуска, время старта урока и время выполнения других важных для приложения задач. Такие показатели сегментировались на основе некоторых параметров. Например, сегментация учитывала экран, на котором находится пользователь, текущий performance mode девайса и тип девайса. Сегментация помогала быстрее обнаруживать причины возникающих проблем и исправлять их.

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

Наш опыт

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

Например, в начале 2022 года в Drum Pads 24 приложении мы внедряли новую возможность, которая позволила бы пользователям создавать полноценные музыкальные треки с помощью программирования последовательностей звуков. После изучения имеющегося кода, который был написан достаточно давно, стало понятно, что внедрить туда новую фичу будет сложно. Мы выделили классы, которые требовали обновления, и выполнили “подготовительный” рефакторинг кода. В рамках него некоторые большие классы были заменены на более компактные с конкретной ответственностью в нужных слоях (UI, Domain, Data). При этом остальная часть приложения осталась без изменений. Результаты этого рефакторинга в приложении были практически незаметны с точки зрения пользовательского интерфейса. Однако для разработчиков польза была. Только после рефакторинга мы внедрили новую фичу. Сложно однозначно сравнить затраты времени, которые бы потребовались на добавление фичи в старый код, с затратами времени на рефакторинг. Но можно точно сказать, что работать с новым кодом было гораздо легче, быстрее и приятнее.

Итоги

Пример команд Headspace и Duolingo показывает, что инвестиции времени команды в обсуждение архитектуры, проработку шаблонов, создание договоренностей окупаются как с точки зрения разработчиков, которые становятся более продуктивными и получают больше удовольствия от ежедневной работы, так и с точки зрения конечных пользователей, которые получают более стабильное и качественное приложение и регулярные обновления с новыми фичами. Однако это не значит, что абсолютно каждой команде нужно делать рефакторинг. Важно понимать, действительно ли это принесет пользу конкретно вашей команде и вашему продукту. Рефакторинг ради рефакторинга не нужен никому и вряд ли даст какие-то улучшения. 

Комментарии (2)


  1. fs353
    05.05.2022 10:52

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


  1. sergeyjojo
    06.05.2022 10:23

    Все достаточно банально. Но все равно спасибо за статью!