Это продолжение нашего рассказа о внедрении Kotlin Multiplatform Mobile в QIWI. Если хотите узнать больше про технику, посмотреть на код, переходите в первую часть. В этой статье будет больше контекста про то, как мы принимали решение, готовили прототип и внедряли технологию в команды. Наш опыт может помочь вам “продать” KMM в вашей компании и вашим стейкхолдерам. Я расскажу о плюсах и сложностях, с которыми мы столкнулись на нашем пути.
Немного истории
В QIWI мы часто пробуем что-то новое в наших процессах и в разработке. За последние 5 лет мы создали несколько приложений с нуля, используя разнообразные подходы. Например, мы впервые попробовали кросс-платформу, когда делали приложение QIWI Инвестор. Мы использовали инструмент J2ObjC, чтобы конвертировать общий модуль с кодом на Java на Objective-c для iOS. Это решение было смелым, но не самым надежным. По ходу использования было много проблем, самые большие — с конвертацией кода сторонних библиотек. В итоге проект закрылся, как и этот эксперимент. Кросс-платформенный подход оказался рабочим, но мы отложили его в сторону пока не появился более надежный инструмент.
В 2018 году мы перешли от платформенных команд к кросс-функциональным. Это стало самым большим изменением в процессе разработки c внедрения скрама. “Совместное владение кодом” стало нашей новой ценностью. Границы между платформами начали размываться, мы стали внимательнее изучать код на других платформах, вместе принимали решения и делились лучшими практиками.
Конечно, на каждой платформе свой стек, своя архитектура, паттерны и лучшие практики. Мы заметили, что мобильные платформы в последние годы развивались практически симметрично: похожая реализация архитектуры MVVM и разделение на слои. Стало казаться, что мы просто дублируем код в двух платформах на нескольких слоях: слое данных, доменном и презентационном. Скачиваем одни и те же json’ы, пишем похожую логику обработки, генерим те же состояния. Каждый спринт мобильные разработчики по отдельности решали одни и те же проблемы, каждый на своей платформе. Возможно, это небольшая проблема, когда вы работаете над простыми фичами и верстаете простые экраны. Но чем сложнее бизнес-логика, чем больше компонентов и сложнее навигация, тем больше сюрпризов может возникнуть.
Из-за того что мобильные разработчики работают не сообща, финальная реализация продукта на Android и iOS может отличаться. Чаще всего страдает дизайн, а именно обработка корнер-кейсов, когда состояния ошибок и загрузок работают по-разному, это может сильно удивить ваших дизайнеров. Иногда это приводит к более существенным багам, когда бизнес-логика производит неконсистентные состояния на двух платформах. Если вы собираете аналитику о действиях пользователя, то нет гарантии, что источником событий являются одни и те же триггеры, из-за чего может пострадать качество ваших данных. Существует довольно большой простор для неправильной интерпретации фич и проблем коммуникации. iOS- и Android-разработчики по отдельности могут выдать совершенно разные решения для одних и тех же задач, что может усложнить поддержку кода в будущем. В итоге дизайнеры, QA и аналитики должны тщательно перепроверять фичи на двух платформах, потому что единое поведение не гарантировано.
Кажется, что переход на мультиплатформу — это очевидный win-win. Общий модуль с данными, доменной и презентационной логикой обеспечит консистентность везде. Но не все так просто.
Для нас все началось с прототипа.
Готовим прототип
В конце 2020 кроме кросс-функциональных команд у нас были выделенные платформенные команды iOS и Android. Они отвечали за технический рост платформы и искали лучшие инструменты для фича-команд, чтобы ускорить цикл разработки продукта. Когда мы решили всерьез попробовать Kotlin Multiplatform Mobile, именно платформенные команды занялись созданием прототипа.
Мы решили, что прототип должен включать в себя самые популярные кейсы в нашей разработке и несколько неочевидных, например, работу с базой данных. Самое главное, что прототип должен был объединить архитектурные подходы на iOS и Android и соответствовать нашим инженерным практикам, чтобы всем было удобно использовать новую базу.
Таким образом, мы пришли к следующим компонентам в прототипе:
Общий HTTP-клиент на основе Ktor.
Базовая архитектура для новых экранов на основе MVI.
Система для логирования и аналитики с базой данных в качестве кэша.
На создание и проверку прототипа ушло порядка трех месяцев. Был создан новый HTTP-клиент, а для асинхронного взаимодействия мы впервые применили корутины. Платформенная команда не только создала скелет новой архитектуры, но также переписала существующий экран приложения, чтобы проверить его в деле. Когда мы убедились, что можем снимать метрики с общего модуля, работа над прототипом была завершена. Даже самый лучший прототип не гарантирует, что переезд пройдет гладко, но у нас получилась достаточно хорошая платформа для старта.
Первые вызовы
Переезд на новый стек — это всегда инвестиция. Как обычно, мы работали в тесном контакте со стейкхолдерами, проговаривали все возможные риски. Мы аккуратно планировали первые спринты с использованием KMM, закладывали больше времени на адаптацию и доводку нашего нового инструмента.
Первые проблемы не стали для нас сюрпризом. Например, мы столкнулись с хорошо известной проблемой concurrent mutablity, когда из нативного кода на iOS пытались модифицировать frozen-объект. Код, который отлично работал на Android, мог легко крешнуться на iOS. Первые подобные баги было сложно поймать, логи содержали далеко не всю информацию о крешах, а выработанной “чуйки” для дебага новой технологии у нас просто не было. Классические варианты дебага работают не так классно, когда вы пытаетесь поймать креш в модуле, который подключен как внешняя библиотека. А после первых релизов мы столкнулись с проблемой некорректных креш-репортов на iOS, но затем нашли хорошее решение в опенсорсе.
Мы начали писать больше интеграционных и юнит-тестов, чтобы как можно раньше убедиться, что общий модуль с KMM работает корректно. Приятно, когда тесты пишутся не ради тестов, а ты сразу ощущаешь профит. Хорошее покрытие тестами — единственный способ убедиться, что общий модуль работает корректно перед отправкой на платформы.
Android-разработчики столкнулись с другой проблемой: когда продолжаешь писать код на котлине в общем модуле, легко забыть, что больше нет доступа к стандартной библиотеке Java. Например, год назад не было поддержки DateTime из коробки, приходилось искать свои пути, как работать с датами. Некоторые проблемы, например, генерация UUID, легко решались через actual/expect-функции.
Наши первые сложности были связаны не только с KMM. Каждый переезд на новую архитектуру сопровождается проблемами, которые проявляются только по ходу использования. Вместе с новыми фичами мы постепенно вносили небольшие изменения в архитектуру. Часто эти изменения происходили после долгих совместных дискуссий. Нас удивило, что переезд на KMM — это вызов не только для ваших хард-скиллов, но и для командной работы.
Разнообразие мнений
С переездом на KMM наши Android- и iOS-разработчики начали писать код вместе и ревьюить код друг друга каждый день. С первого взгляда кажется, что у мобильных разработчиков много общего: мы делаем один продукт, похожие приложения, решаем очень похожие проблемы, красим одни и те же кнопки. Но мы быстро заметили, что во многих решениях наши взгляды немного отличаются. При этом для каждой стороны эти решения были уже давно проверены в продакшене и закреплены в инженерных практиках. ViewModel на Android и на iOS, это ведь не одно и то же, правда? Иногда даже небольшие изменения в новой архитектуре приводили к долгим дискуссиям. Например, мы обсуждали, что стоит включать во вьюстейт, как его менять, являются ли всплывающие диалоги частью вьюстейта, как валидировать пользовательский ввод, как передавать данные между вьюмоделями и как шарить ресурсы, и подобное.
Все эти проблемы не связаны напрямую с внедрением KMM, скажу очевидную вещь: больше совместной работы — больше мнений. Теперь мы смотрели на каждую проблему с большего числа углов и генерили больше возможных решений. Не все наши решения работали на ура с первого раза, но мы учились работать и экспериментировать вместе, и в итоге всегда получали лучший результат. Это положительно повлияло на качество кода и продукта.
О будущем
За последний год каждая новая фича в Кошельке была создана при помощи KMM. Мультиплатформа хорошо прижилась в наших инженерных практиках и культуре фича-команд. Мы уже делились своим опытом на митапе, а если хотите больше технических деталей, посмотрите прошлую статью. На нашем пути все еще остаются проблемы, которые мы разбираем в своем темпе. О большей части из них уже в курсе команда JetBrains, которая каждый день работает над улучшением мультиплатформы. Кстати про JetBrains, в начале года нам удалось встретиться с командой KMM из JetBrains, мы поделились своим опытом, получили несколько ценных советов и узнали пару инсайдов о будущем. Ребята поразили нас своей отзывчивостью и глубокими знаниями.
KMM — это еще и большое комьюнити, и в опенсорсе уже есть много отличных решений для разных проблем, например для работы с ресурсами. Это даёт нам уверенность, что технология будет жить и развиваться. Мы рады быть на одной волне с большим количеством мировых компаний, адаптирующих технологию, которая может стать отличным фундаментов для будущего мобильной разработки.
Если вам близок наш подход к разработке, можете оттачивать свои навыки в рамках наших команд — мы ищем разработчиков на Android и iOS.
До связи!