Ask me anything! Задай вопрос Android-команде Badoo
Какую архитектуру вы используете? Нравится ли она вам и что бы вы изменили, если бы могли? Какие уроки вы извлекли?
Жольт: Мы используем сильно переделанную версию RIBs (под «сильной переделкой» я подразумеваю «В этой ветке 871 коммит и 15 коммитов после uber: master»). Получилась древовидная структура, каждый слой которой можно взять и вставить в другое приложение со всеми связанными с ним ветками. В узлах дерева мы применяем паттерн MVI с реактивными биндингами. Мы довольны тем, что получилось!
Какие уроки мы извлекли. Activity — это неверная абстракция. Если хотите использовать компоненты многократно, то навигация не должна быть глобальной. Сильно помогает удаление из компонентов понятия экрана. Наличие определённой структуры для создания переиспользуемых компонентов очень помогает нам понимать код друг друга. Всю тяжёлую работу прячьте за абстракциями, чтобы клиентский код было легче писать и сопровождать.
Майкл: Недавно наша команда Revenue Team начала экспериментировать с новым подходом на основе MVI, который мы назвали SubFlow. Большое влияние на него оказал паттерн с акторами (как это видно на примере библиотек Play Framework и Vert.x). Изначально этот подход применялся нашей iOS-командой. Увидев, как он хорошо работает, мы решили тоже попробовать. Суть в разделении бизнес-логики на простые одношаговые акторы. Каждый актор/подпроцесс для выполнения следующего шага может запустить следующий подпроцесс в цепочке. Возможные варианты подпроцессов конфигурируются извне.
Нашей главной задачей было уменьшить сложность и громоздкость кода, а также обменяться опытом с iOS-командой. Работа ещё в стадии эксперимента, но результаты нам уже очень нравятся. К сожалению, код пока закрыт.
Наверняка у вас есть древний код. Как вы относитесь к рефакторингу старых решений, чтобы идти в ногу со временем? Если рефакторите, то как? У вас этим занимается специально обученный человек или целая команда?
Анатолий: Если при разработке новой функциональности приходится изменять уже существующий код, то мы можем вносить в оценку задачи рефакторинг старой части кода. Кроме того, если мы сталкиваемся с проблемами в кодовой базе, то обсуждаем с руководством выделение времени на рефакторинг. По умолчанию мы тратим на это около 20% времени. Рефакторить может кто угодно в зависимости ситуации, у нас нет для этого специального человека.
Антон: Я стараюсь соблюдать правило скаута: «Оставляй после себя код лучше, чем он был, когда ты его нашёл». Работая над фичей, которая затрагивает старый код, я стараюсь улучшить покрытие тестами; возможно, делаю небольшой рефакторинг, например разбиваю длинные методы. А для более масштабных переделок мы составляем конкретный план, выделяем на это время и определяем этапы.
Майкл: У нас есть части кода, написанные в далёком 2012 году. Мы оставили их нетронутыми, потому что было страшно менять их без покрытия тестами. То есть мы сначала улучшаем покрытие, в этом нам помогает наша замечательная команда тестировщиков, которая с помощью Calabash создаёт для нас end-to-end-тесты. Затем мы начинаем маленькими кусочками переписывать старый код, часто объединяя это с работой над фичами. Наконец, мы специально выделяем время на переписывание самых «плохих» частей. В соответствии с правилом команды Revenue Team мы тратим один день в неделю, чтобы держать под контролем технический долг, выбирая для этого какую-нибудь необычную задачу.
Какую БД вы используете в приложении?
Аркадий: Мы используем SQLite: либо базовый SQLiteOpenHelper, либо Room. Последний мы используем только в новых модулях, над которыми работаем в данный момент. Переводить на Room другие части приложения (например, чат), которые уже используют SQLiteOpenHelper, нецелесообразно.
Как вы относитесь к Annotation Processing?
Аркадий: Отрицательно. Плагины для компилятора — наше будущее!
Николай: Мы стараемся избегать обработки аннотаций и для тестовых сборок по мере возможности используем реализации библиотек обработки аннотаций с поддержкой рефлексии. Сейчас мы применяем обработку аннотаций для Dagger, Room и Toothpick.
Андрей: Apt годится до тех пор, пока это не kapt.
Проект сегментирован по странам? Как это сделано?
Иван: С нативной клиентской точки зрения это выглядит как единый бэкенд, к которому мы обращаемся через API. Серверная часть разделена на несколько фрагментов, но всё не так просто, потому что функциональность одновременно используется разными сервисами и уровнями приложения.
Николай K. (серверная команда): У нас два основных региона (два местоположения дата-центров). Каждый пользователь обслуживается дата-центром из его первичного региона. Этот регион определяется на основе местоположения пользователя, указанного им при регистрации. Позднее регион может меняться (когда пользователь в офлайне), если пользователь сменил местоположение.
Ваш проект использует App Bundle? Какой был выигрыш в размерах .apk?
Николай: Да, мы используем App Bundle. Размер приложения с использованием App Bundle уменьшился примерно на 17%.
Андрей: Также мы применяем Dynamic Delivery и даже написали об этом статью.
Обсуждала ли команда миграцию на мультиплатформенный подход? На какой именно или почему нет?
Аркадий: Мы экспериментировали с Kotlin Multiplatform, у нас есть целиком написанный на нём внутренний проект, и мы обсуждали использование этой технологии в продакшене. В нашем основном проекте тоже есть несколько мультиплатформенных модулей, но сейчас они используются только в Android.
Для поддержки перехода мы создали Reactive Extensions-библиотеку Reaktive.
MVICore сейчас переводится на Kotlin Multiplatform.
Может ли команда рассказать о каком-нибудь маленьком изменении или новой фиче, которая продемонстрировала большие результаты?
Анатолий: Что касается фич, то мы реализовали в Badoo видеочаты и оптимизировали функцию отправки подарков. Раньше пользователю нужно было нажать на кнопку трансляции, затем на «Отправить подарок» и выбрать подарок. Целых три действия — и людям не было очевидно, что есть такая возможность. Поэтому мы добавили нижнюю панель с горизонтальным списком подарков, чтобы пользователи могли выбрать подарок одним нажатием и сразу же отправить его стримеру. На внедрение ушло около трёх дней, но позже мы получили значительное увеличение числа отправленных подарков.
Майкл: В левом верхнем углу приложения мы показываем количество лайков, которые получил пользователь. Раньше это было визуализировано в виде точки, которая с помощью анимации отображала количество лайков. Мы убрали анимацию и оставили просто число, в результате чего метрики заметно улучшились.
Используете ли вы Android Jetpack, Fragments и Activities? Или что-то вместо них?
Жольт: Хороший вопрос!
Ограничения: некоторые из подходов Jetpack плохо масштабируются до архитектуры нескольких приложений. Думаю, это проблема редкая, но для нас она является серьёзным препятствием.
LiveData: нет. Вместо MVVM у нас MVI, и мы разработали для неё собственный инструмент для автоматического определения области видимости — Binder. Сейчас он существует как часть нашей библиотеки MVICore, но мы планируем сделать его в виде отдельной библиотеки. Так будет универсальнее по сравнению с LiveData, поскольку в этом случае Binder можно будет использовать и вне контекста Android, причём очень легко (одна строка на Kotlin). Почитать об этом подробнее можно здесь и здесь. Действительно отличный инструмент. Попробуйте его.
Компонент Navigation: нет. Мы применяем паттерн Router со своей версией RIBs. Необходимость поддерживать глобальную навигацию в приложениях с общими компонентами затрудняет сопровождение на уровне приложения, а также требует понимания устройства конкретных компонентов. Последнее является огромным недостатком, если вы хотите переиспользовать какой-то компонент в разных приложениях. Компонент не должен что-либо предполагать относительно приложения, которое его использует (например, какие размеры экранов доступны). Routing, к примеру, это просто локальная навигация. Он переносит задачу навигации с глобального уровня на уровень реализации компонентов, поэтому вы можете спокойно использовать их где угодно.
Fragments: нет. С помощью RIBs мы создали что-то похожее на глубоко вложенное дерево фрагментов, но гораздо лучше. Также мы без каких-либо хаков решили проблему безопасного внедрения конструкторов вроде Fragment Factory на этапе компиляции. Фреймворк собирает компоненты за вас, но вы говорите ему, как это делать.
Почему мы пошли своим путём, а не следуем общепринятым практикам? Дело в том, что этот процесс начался много лет назад. Задолго до наступления эры Jetpack мы пытались идти по «пути Google» и в результате обожглись на Fragments. Ушли от этого, стараясь понять, какая альтернатива подойдёт нам лучше всего. Часто мы были одними из первых, кто внедрял у себя новую технологию (в 2016-м мы попробовали «чистую» архитектуру и RxJava, в 2017-м — Kotlin и MVI на основе Redux), и подобных инструментов, фреймворков и библиотек было не так уж много. К моменту анонса Jetpack мы уже вложились в собственный технологический стек. И в целом он вполне нас устраивает в сравнении с общепринятыми подходами.
К слову, мы используем Room, и лично меня очень интересует Jetpack Compose.