Привет! Меня зовут Азат Зулькарняев, я занимаюсь разработкой iOS-приложений в компании Badoo. При создании мобильных приложений большая часть времени уходит на разработку UI, и оптимизация этого процесса всегда является актуальной темой в среде разработчиков. Мой коллега Алексис Сантос написал статью о том, с какими проблемами мы столкнулись и как двигались в сторону их разрешения при работе над этой задачей. Я решил поделиться с вами переводом. Также рекомендую посмотреть запись недавнего доклада Игоря Савельева на Mobius 2018.
Несколько месяцев назад я наткнулся на очень интересный документальный сериал от Netflix — «Абстракция: Искусство дизайна». В нём подробно рассматривается работа дизайнеров из разных сфер: архитектура, графический дизайн, мода и т. д. Нетрудно заметить определённое сходство их работы с деятельностью iOS-разработчиков, занимающихся реализацией пользовательских интерфейсов. В частности, ведя работу над крупным проектом, дизайнеры стараются разбить её на множество мелких задач по принципу «разделяй и властвуй» и получают возможность собрать все элементы воедино на более позднем этапе.
Такое разделение даёт возможность сконцентрироваться на отдельной проблеме, не беря во внимание взаимосвязь компонентов. Тем не менее совсем забывать об общей картине нельзя — иначе, когда придёт время сборки, могут возникнуть трудности.
С другой стороны, в процессе просмотра я заметил, что окончательный дизайн многих продуктов — обуви, постеров или зданий — не претерпевает со временем никаких изменений. Nike, отправив новые кроссовки на полки магазинов, больше не будет обновлять их внешний вид. В некоторых случаях продукт выглядит всё так же здорово и через 20 лет. К сожалению или к счастью, к дизайну мобильных приложений этот принцип неприменим — разработчики должны быть готовы к частым изменениям внешнего вида своих продуктов.
Nike Air Max 97, Empire State Building (Нью Йорк)
Многие iOS-разработчики, работая над крупными и сложными приложениями, тратят много времени на создание интерфейсов, определение взаимосвязей объектов и оттачивание мелких деталей. Для эффективной работы необходимо держать в уме то, как должно выглядеть приложение в целом, и столь же важно думать о его разделении на отдельные компоненты, которые впоследствии переиспользуются при разработке элементов приложения.
Наша команда выпускает новые релизы еженедельно, и каждый из них содержит новые функции, улучшения и другие изменения, влияющие на пользовательский интерфейс. Мы всегда стремимся работать максимально быстро и качественно, однако год назад в процессе разработки UI обнаружили несколько проблем.
Проблематика
Если вкратце, то процесс нашей UI-разработки не был чётко структурирован.
На практике это означало, что запуск любой новой функции мог привести к самым непредсказуемым последствиям. Например:
- не было чётких правил взаимодействия с дизайнерами: коллеги не всегда были в курсе нашей работы, и это приводило к тому, что они создавали новые компоненты, по сути, аналогичные уже существующим, но выглядящие по-другому;
- у нас не было универсальной модели реализации новых UI-компонентов: каждый разработчик реализовывал компоненты по-своему, что усложняло поддержку проекта;
- дублирование кода тоже не делало кодовую базу лучше.
Как следствие:
- чтобы внести изменения в один UI-элемент, нужно было менять код в во многих местах проекта;
- повышенный риск сломать части приложения в тех случаях, когда обновления их не касались и их работа не тестировалась.
Конечно, все эти проблемы не могли исчезнуть по мановению волшебной палочки. Чтобы изменить порядок вещей, нужно было прийти к согласию между различными командами и убедить всех участников процесса, что перемены необходимы.
Как нельзя кстати пришёлся принцип «разделяй и властвуй». Мы начали с малого, поочередно выделили проблемы, после чего шаг за шагом пришли к глобальному решению. Ниже я расскажу, как нам это удалось.
UI-фреймворки
Мы подошли к решению проблемы со всей серьёзностью и начали с основ. Прежде всего нужно было избавиться от дублирования кода. Для этого мы унифицировали используемые компоненты, собрав их воедино.
Мы решили создать пару фреймворков, которые назвали BadooUIKit. По нашей задумке, они должны содержать все необходимые UI-компоненты (наподобие UIKit от Apple). Каждый из классов фреймворка соответствует UI-элементу какого-либо приложения (наша компания разрабатывает и другие приложения, но в данный UIKit мы добавили только компоненты, использующиеся в Badoo).
Каждое приложение обладает собственными шрифтами, расцветкой, оформлением полей и другими атрибутами, а потому очень полезно иметь таблицу стилей, относящуюся к конкретному приложению.
Но как быть, если тот или иной UI-компонент можно использовать в разных приложениях?
На этот случай мы создали ещё один фреймворк — Platform_UIKit. В нём содержатся все компоненты, подходящие для других приложений.
Вы просто разом перенесли все UI в новый фреймворк?
Нет, это весьма проблематично. Вместо этого мы создавали каждый новый UI-элемент внутри фреймворка, а уже существовавшие компоненты переносили, только если затрагивали их в рамках работы над своими задачами. Иногда компонент было трудно перенести из-за слишком большого количества зависимостей — тогда мы занимались им отдельно. Прежде всего мы переносили базовые элементы вроде шрифтов, цветов и кнопок. Затем, возведя фундамент, мы перенесли во фреймворк весь интерфейс нашего чата. Чтобы было легче, мы сделали это уже после создания инфраструктуры.
Офтоп: если вас интересует процесс создания компонентов, ловите классную статью моего коллеги Валерия Чевтаева myltik.
Одно из главных требований к фреймворкам — отсутствие зависимости от других фреймворков и классов, не относящихся к UI. В частности, мы никогда не импортируем модели из основного приложения, относящиеся к сетевому уровню классы, аналитику и т. д. Благодаря этому мы получаем возможность повторного использования компонентов:
Мы можем импортировать данные из Platform_UIKit в BadooUIKit, но не наоборот: Platform_UIKit должен сохранять независимость.
Создание этих фреймворков не потребовало особого труда, равно как и их последующая поддержка. Каждый новый проект Badoo отличается от предыдущего, и нам бывает непросто поддерживать описанную структуру, но наше решение принесло пользу как в ближайшей, так и в отдалённой перспективе.
Плюсы использования UIKit:
- хранение всех UI-компонентов в одном месте облегчает их поиск; соответственно, работа становится более организованной;
- освобождение классов от зависимостей помогает сократить время компиляции;
- устранение зависимостей способствует повторному использованию компонентов и тоже ускоряет компиляцию;
- обновляя компонент в BadooUIKit, мы обновляем его повсюду; если приложение использует компоненты из BadooUIKit, упрощается процесс внесения изменений в компоненты по всему приложению;
- изолированные компоненты гораздо легче тестировать;
- отдельный фреймворк при необходимости можно использовать и в других приложениях (например, при создании приложения — каталога всех компонентов данного фреймворка).
Создание Badoo Gallery
BadooUIKit помог нам решить значительную часть проблем, но мы понимали, что нет предела совершенству.
Как увидеть все UI-компоненты по отдельности? Можно ли настроить поиск компонентов и увидеть каждый из них в различных цветовых гаммах? Можно ли облегчить их тестирование? Можно ли создать для дизайнеров каталог всех существующих и реализованных UI-компонентов?
Запустив BadooUIKit, мы решили создать простенькое отдельное приложение-каталог для использования внутри компании. Так появился Badoo Gallery.
Badoo Gallery — инструмент, помогающий разработчикам, дизайнерам и даже членам продуктовой команды увидеть все UI-компоненты в наглядной, доступной форме. При его создании мы пользовались самыми разными средствами, которые облегчают взаимодействие с компонентами.
Поскольку наше приложение не было предназначено для публикации в App Store, мы могли добавить любой инструмент, какой считали нужным. В качестве ключевых мы выделили следующие функции:
- поиск компонентов;
- сортировка компонентов по названию;
- добавление элементов в избранное;
- переключение между стилями — чтобы можно было посмотреть, как компонент будет выглядеть в том или ином оформлении;
- FLEX;
- счётчик FPS.
Каждый компонент может находиться в разных состояниях в зависимости от действий пользователя и внутренней логики приложения. Например, UIButton имеет пять состояний: 1) по умолчанию, 2) выделенное, 3) при наведении, 4) при нажатии и 5) заблокированное.
Интересно? Подробнее читайте здесь.
Кроме того, нам хотелось иметь возможность представить все возможные комбинации в одном месте — мы поступаем так с каждым экраном каждого компонента. Конечно, состояния наших кнопок могут отличаться от состояний кнопок UIKit Apple.
Основные преимущества Badoo Gallery:
- возможность создания перечня реализованных UI-компонентов;
- лёгкий поиск UI-компонентов: каждый из нас может увидеть все возможные варианты внешнего вида UI-компонента и найти им применение;
- облегчённый поиск уже существующих компонентов помогает убеждать дизайнеров использовать их повторно;
- время компиляции такого маленького приложения очень мало, это помогает значительно сократить период разработки;
- функция избранного помогает найти компоненты, реализованные в данный момент;
- добавление внешних инструментов вроде FPS, FLEX и MultiBrand позволяет измерять качество UI-компонентов и совершенствовать их;
- все компоненты помещены в отдельный фреймворк и представлены в изолированной среде — тестирование стало гораздо проще.
Немного о тестировании
Новые инструменты помогли решить большинство проблем, описанных в начале статьи, но некоторые вопросы остались без ответа.
Можем ли мы быть уверены в том, что после внесения изменений UI будет выглядеть так, как мы задумали? Как защитить компоненты и другие части приложения от неблагоприятного воздействия новых параметров подкомпонентов?
Найти ответы на эти вопросы помогут тесты UI-компонентов. В Сети немало статей об организации UI-тестирования на iOS. Кроме того, существует множество различных инструментов, предназначенных для тестирования тех или иных сторон интерфейса.
Мы решили проводить snapshot-тестирование, внедрив одну из самых популярных в то время open-source-утилит iOSSnapshotTestCase (ранее известную как FBSnapshotTestCase, поскольку создана она была компанией Facebook).
Узнать больше о тестировании с использованием скриншотов и об этом фреймворке вы можете по одной из этих ссылок:
- https://github.com/uber/ios-snapshot-test-case/
- https://www.objc.io/issues/15-testing/snapshot-testing/
- https://ashfurrow.com/blog/snapshot-testing-on-ios/
Нам нужен был способ тестирования уже имевшихся в BadooUIKit компонентов, чтобы избежать регрессии при обновлении компонентов приложения. Мы также хотели по максимуму автоматизировать процесс внедрения новых snapshot-тестов.
Выше я уже рассказывал о созданной нами галерее, содержащей перечень всех компонентов и состояний, которые может принимать каждый компонент. Это очень удобно, поскольку в этом случае тесты могут запускаться на базе Badoo Gallery как хост-приложения.
Все находящиеся в BadooUIKit компоненты содержатся в классе-репозитории, предоставляющем доступ ко всем компонентам. Этот репозиторий способен как демонстрировать список компонентов в галерее, так и открывать доступ к ним при помощи классов snapshot-тестов. Это освобождает нас от двойной работы — создания объектов и подготовки различных состояний для каждого из компонентов — поскольку всё это уже было сделано, когда мы вводили компонент в галерею.
Вот ответы на самые распространённые вопросы о тестировании со снимками.
Где хранятся снепшоты?
Мы храним их прямо в Git-репозитории. Мы опасались, что это может привести к его раздуванию, но на деле всё оказалось не так плохо. Как правило, мы тестируем небольшие компоненты, а потому скриншоты весят очень мало. На данный момент папка со скриншотами занимает около 11 Мб, что, как нам кажется, терпимо.
Вы тестируете все возможные разрешения во всех возможных окружениях?
Нет, пользы от такого подхода мало. А вот проблем может быть достаточно: тесты станут ненадёжными, папка со снимками — более объёмной, а тестовый набор будет сложнее поддерживать. Мы прагматично проводим тесты только для самых популярных устройств. Кроме того, наша система CI настроена на использование того же симулятора, который использовался для создания снепшота.
Способны ли snapshot-тесты охватить весь интерфейс?
Думаю, что нет. Мы в Badoo проводим различные тесты для различных уровней приложения. Например, функциональные (при помощи фреймворков Calabash и KIF) и интеграционные.
Выводы
Разумеется, в ходе построения новой платформы мы многому научились — и продолжаем учиться. Описанные выше инструменты и процессы появились около года назад и до сих пор развиваются. На данном этапе можно заключить, что все они приносят пользу разработчикам и компании в целом.
Вот некоторые из уроков, которые мы усвоили в ходе работы:
- перенос всех действующих компонентов — нелёгкая задача, но, реализовав систему проектирования и предложив команде её использовать, вы увидите, как быстро вырастет число компонентов: перенос используемых разработчиками компонентов не только автоматически увеличивает количество переиспользуемых UI-элементов вашей системы, но и помогает существующим элементам освободиться от зависимостей; в будущем это позволит переносить больше компонентов;
- дизайнерам нравится повторно использовать компоненты (убедить их делать это становится проще, когда есть возможность показать полностью рабочий компонент, отвечающий их требованиям);
- экономить время необходимо; все мы знаем, что длительность компиляции проектов на Swift и Objective-C оставляет желать лучшего, при этом приложение Badoo Gallery легковесно и очень быстро компилируется; мы поняли, что гораздо удобнее реализовывать UI-компоненты непосредственно с помощью галереи, а затем пользоваться ими через основное приложение, где компиляция идёт не так быстро;
- объединив все компоненты в UIKit и запустив приложение-галерею, в котором их можно тестировать, мы сделали весь процесс тестирования гораздо эффективнее и проще.
Дальше — Cosmos
Мы уделяем много внимания каждому из наших продуктов и хотим, чтобы все они обладали универсальным и привлекательным интерфейсом. Для того чтобы наши пользователи получали максимальное удовольствие от их использования, мы решили произвести глобальные перемены. При содействии дизайнеров и продуктовой команды мы вводим новую единую систему проектирования, получившую название Cosmos.
Кристиано Растелли написал несколько увлекательных статей о том, как появилась система Cosmos. Не пропустите!
Благодарности
Работу над проектом вёл не один человек — в той или иной степени в ней участвовала вся iOS-команда Badoo, включая менеджеров, разработчиков и тестировщиков. Я благодарю их всех, ведь каждый из них был на борту с самого начала пути.
Спасибо нашим потрясающим дизайнерам, которые всегда готовы приложить максимум усилий для усовершенствования процесса проектирования.
Особая благодарность Александру Зимину: за множество предложенных улучшений, посещение бесчисленных планёрок, а также за поддержку, оказанную мне в этом приключении.
Также благодарю Алиссу Ордильяно за прекрасные иллюстрации, сделавшие эту статью доступнее.
Автор: azat_z