В активно развивающихся приложениях порой сложно обеспечивать масштабируемость, удобочитаемость и качество кода. Это частая ситуация, которая со временем может усложнить поддержку, выявление проблем и их устранение. Избежать подобных проблем помогает модуляризация приложений — подход, при котором кодовая база делится на небольшие специализированные, готовые к повторному использованию модули.
Меня зовут Кирилл Смирнов. Я технический лидер iOS команды в СберЗдоровье. Последний год наша команда плотно занималась улучшением инструментов разработки, в том числе модуляризацией. В этом материале я поделюсь опытом и рекомендациями по подготовке бизнеса (заказчиков, исполнителей, смежников и др.) к модуляризации iOS приложений.
Статья написана в рамках серии «Многомодульное приложение: оно вам надо?».
Модуляризация и ее особенности
Модуляризация — процесс разделения кода приложения на части. По своей сути он идентичен архитектуре микросервисов на бэкенде — привычный алгоритм взаимодействия между сервисами трансформируется во взаимодействие между модулями.
Модуляризация — сложный процесс, у которого есть несколько особенностей:
Растянутость во времени. Модуляризация — отдельный процесс в работе вашего отдела, который будет требовать регулярной поддержки. Быстро и просто его не выполнить.
Разнородность подходов. Альтернативных подходов модуляризации много, и универсального нет. Поэтому команде уже на старте надо сравнить варианты и выбрать оптимальный под свои запросы и задачи.
Зависимости от внешних факторов. На ход модуляризации могут влиять как внешние, так и внутренние факторы, даже такие неочевидные, как структура компании и размер отдела разработки. Каждый проект уникален и это нужно учитывать.
Подробнее о влиянии организационной структуры компании на кодовую базу проекта
В 2009 году в книге «Начни с почему?» Саймон Синек предложил термин Golden circle, в основе которого три вопроса: «почему», «как», «что» (Why, How, What).
С помощью описанного автором золотого кольца можно объяснить ключевые и базовые моменты, которые относятся как к бизнесу, так и к другим сферам. Например, почему компания делает то, что делает, во что верит и какие ценности несет. При этом важно, что переходить к следующему циклу нельзя, пока не пройден предыдущий.
Аналогичный подход, но применительно к разработке, описан и в книге Сэма Ньюмена «Создание миĸросервисов». Его мы использовали при реализации своего проекта. Как и в первом случае, подход подразумевает прохождение трех этапов.
Why — стратегическая цель компании. В нашем случае предпосылкой к модуляризации приложения стал запрос от компании, в соответствии с которым нам надо было обеспечить:
прозрачную структуру команды;
Code Ownership;
поставку множества функций одновременно без увеличения времени между релизами.
How — требования к производству. Далее, совместно с бизнесом, были выработаны требования к производству. Исходя из требований, мы выделили необходимость обеспечения:
автономности команд;
скорости и качества разработки;
культуры автоматизации;
мониторинга и быстрого реагирования на инциденты.
What — инструменты разработки. После формализации требований мы определили инструменты разработки, которые описывают, как будут удовлетворяться поставленные перед системой требования. Основные из них:
автоматизация установки окружения;
создание нового модуля и универсального шаблона модуля;
формализация технологического стека;
обеспечение изолированности модулей;
определение общих библиотек;
выбор подхода к версионированию и релизам модулей, а также другие методы.
Таким образом, правило Golden circle и концепция, описанная в книге «Создание миĸросервисов», помогли нам пройти путь от выявления потребности в модуляризации до выбора нужных для этого инструментов.
Предпосылки разделения приложения на модули
В нашем случае запрос на модуляризацию шел от бизнеса. Но так бывает не всегда — часто команде разработки нужно доказывать потребность в разделении приложения на модули и выделении ресурсов под эти задачи.
Чтобы понять внутри команды разработки и доказать бизнесу, что модуляризация нужна, достаточно выработать собственный список вопросов-маркеров и ответить на них.
Мы провели такой анализ и для удобства оценки разделили вопросы на несколько категорий.
Коммуникации и HR-процессы:
Есть ли проблемы коммуникации в команде?
Есть ли проблемы с онбордингом новых членов команды?
Как часто возникают проблемы с потенциальными кандидатами из-за несоответствия технологического стека?
Возникают ли проблемы с поиском ответственного за кусок кода?
Технические причины:
Есть ли большая система, которую оптимально разбить на части?
Описаны ли все подходы взаимодействия разных фичей или экранов?
Есть ли куски кода, которые можно считать Legacy и которые никто не меняет?
Можно ли безболезненно внести изменения в участок кодовой базы и самостоятельно развернуть изменения?
Скорость разработки:
Часто ли приходится компилировать все приложение, чтобы протестировать всего одно небольшое изменение?
Возникают ли проблемы со скорость поставки фич при надлежащей работе?
Как часто возникают и перезагружаются красные пайплайны на CI?
Необходимость изоляции технологий:
Часто ли возникает вопрос о переходе на новый инструмент?
Есть ли в проекте устаревшие технологии?
Есть ли члены команды, препятствующие использованию новых технологий в новых задачах из-за консервативности?
Качество разработки:
Есть ли в приложении «костыли», уязвимости или места, которые можно доработать?
Утвердительные ответы на подобные вопросы — признак того, что работу команды и приложение можно улучшить и сделать это именно с помощью модуляризации.
Приведенный список вопросов не универсальный — как и писал выше, у каждого он будет свой в зависимости от специфики компании.
Подготовка к модуляризации: основные рекомендации
Чтобы модуляризация приложения прошла легко и не провоцировала появление сопутствующих сложностей, перед ее началом лучше провести небольшую подготовку.
Автоматизируйте настройку окружения. Это поможет не отвлекаться в ходе работы на проблемы на CI и у отдельных членов ĸоманды, а также упростит онбординг новых членов ĸоманды. В нашем случае все окружение устанавливается одной командой, а специалистам доступна подробная документация. Результат — окружение вплоть до версии Xcode у ĸоманды и на CI идентичное.
Наш пример универсальной утилиты для установки всего окружения
function bootstrap {
log "bootstrap started!"
# setup in project_folder
homebrew_install
zshrc_change 'eval "$(brew shellenv)"' false
bash_profile_change 'eval "$(brew shellenv)"' false
rbenv_install
zshrc_change 'eval "$(rbenv init -)"' false
bash_profile_change 'eval "$(rbenv init -)"' false
log "ruby -v: $(ruby -v)"
ruby_install
bundler_install
bundle_install
swiftlint_install
graphviz_install
xclogparser_install
xcodegen_install
xcodegen_generate
bundle exec pod repo update
pods_install
create_hooks
log_ok "bootstrap successfully finished!"
}
# Обязательно не забудьте добавить документацию по скрипту
function help {
HELP="
Usage:
$ PATH_TO_BOOTSTRAP COMMAND
$ e.g: ./SberHealth/scripts/bootstrap.sh COMMAND
Commands:
Bootstrap:
+ bootstrap Для полного прогона инициализации проекта (с обновлением репо подов), вызываем в корне репозитория
Homebrew, Ruby и Gems:
+ homebrew_install Для установки или обновления homebrew, вызываем в корне репозитория
+ ruby Для установки или обновления ruby, вызываем в корне репозитория
+ bundler Для установки bundler-а
+ bundle Для установки гемов, вызываем в корне репозитория
Swiftlint:
+ swiftlint Для установки swiftlint, вызываем в корне репозитория
+ realpath Для установки realpath (нужен для скрипта ./ci_scripts/swiflint.sh)
Git:
+ clean_gitignore Для очистки репозитория от файлов лежащих в gitignore
Xcodegen и cocoapods:
+ xcodegen Для генерации проектных файлов и установки подов/генерации воркспейса
+ xcodegen_with_repoupdate Для генерации проектных файлов и установки подов/генерации воркспейса с обновлением репо подов
+ generate_mocks Для генерации моков
+ pods Для установки подов в проекте, вызываем в корне репозитория
+ pods_with_repoupdate Для обновления репо подов и установки подов в проекте, вызываем в корне репозитория
+ graphviz Для установки graphviz чтобы строить граф зависимостей
+ xclogparser Для установки xclogparser чтобы собирать информацию по времени билда
+ create_module Для создания нового модуля. Передайте аргументом имя модуля
+ swift_build_system Для включения нового мода сборки.
Xcode и симуляторы:
+ xcode Для установки актуальной версии Xcode и симуляторов вызываем (на основе .xcode-version и .simulators-version)
VSCode:
+ vscode_support Для настройки окружения vscode
Расширенная документация: https://docdoc.atlassian.net/wiki/spaces/MobileDev/pages/3151003832/iOS+CI.+iOS
"
log_info "${HELP}"
}
Заранее постройте пайплайн. Это поможет понять, как поддерживать стабильность модулей, сколько времени нужно на сборку и тесты, какие инструменты нужны и так далее. Ну и конечно, пайплайн будет полезен не только при подготовке и в процессе модуляризации, но и после нее.
Внедрите кодогенерацию xcodeproj файлов. Она позволит предотвратить множество конфликтов в коде (добавить файлы проектов в .gitignore) и, как результат, ускорит модуляризацию.
*.xcworkspace
*.xcodeproj
**/Info.plist
Ориентируйтесь на бизнес. Некоторые требования к модуляризации могут исходить из бизнес-концепции. Поэтому важно придерживаться закона Конвея.
Например, основой нашего бизнеса является телемедицина, но вместе с ним есть и другие бизнес-вертикали. Их сочетание формирует сложную организационную структуру с множеством независимых фиче-команд, которая непосредственно влияет на организацию кодовой базы и взаимодействие модулей между собой.
Подробнее о фиче-командах читайте в наших статьях здесь и здесь.
Соблюдение закона Конвея нашими фиче-командами дает ряд преимуществ:
Повышается качество кода и код-ревью — команды отвечают за отдельные сервисы или модули, что повышает ответственность, но позволяет уделять ему больше времени.
Растет автономность и скорость поставки — команды работают в изолированных контурах.
Повышается продуктивность — существует эффеĸт Рингельмана, ĸоторый описывает тенденцию ĸ снижению личной продуĸтивности отдельных членов команды по мере роста её численности.
Коммуникации становятся проще — в командах мало людей, поэтому проще выстраивать общение и взаимодействие. Это же подтверждает и подход 2pizza, используемый компанией Amazon: чем больше людей — тем больше общения и оно становится сложнее.
В соответствии с упомянутыми подходами, наша команда разделена на платформенную, которая предоставляет инструменты для других команд, и четыре фиче-команды, которые отвечают за:
мониторинг здоровья;
медкарту;
телемедицину;
анализы и чекапы.
Подробно и доступно влияние реального мира на код описано в книге Эриĸа Эванса «Предметно-ориентированное проеĸтирование» — рекомендую пытливым умам.
Сразу отмечу, что структура компании и бизнес-границы команды — не единственные причины модуляризации приложений. Есть и другие предпосылки:
Переиспользование модулей. Например, если одну из функций использует несколько приложений или команд, ее целесообразно выделить в отдельный модуль, а не дублировать каждый раз.
Частые изменения кода. Например, когда один фрагмент кода часто меняют, его можно выделить в отдельный модуль. В таком случае тестировать обновление надо будет не на всем коде, а только на небольшом фрагменте, что дешевле, легче и быстрее.
Необходимость изоляции. Например, если есть большой объем задач с отдельной частью приложения. С модуляризацией можно изолировать окружение и работать, не задевая других. Также изоляция и работа в замкнутом контуре поможет абстрагировать технологии для разных сервисов внутри приложения и соблюдать повышенные требования безопасности.
Слабые зависимости. Например, если код написан хорошо и имеет мало жестких связей, то его разделение на модули упростит работу и доработку приложения до целевого вида. Аналогично и со сложными зависимостями — модуляризация поможет построить понятную сеть связей.
Что дает модуляризация
Для наглядности приведу пример модуляризации синтетического проекта, похожего по структуре на наш.
Есть монолитное приложение, в котором много связанных между собой частей кода — модулей. Часто в такой структуре нет системности — все взаимодействие организовано субъективно, без явных границ. Работать с такой кодовой базой сложно, а находить и устранять ошибки долго.
Первый этап при модуляризации приложения — переход к слоистой архитектуре с разделением модулей на основе бизнес-концепций. Причем модули могут быть как flow (например, экраном или фичей со своим бизнес-контекстом), так и абстрактными, решающими отдельные задачи (например, сторис или модуль платежей). Такие модули можно использовать не только в рамках супераппа, но и поставлять в соседние приложения для сокращения time to market. При этом разделение модулей по слоям будет уникальным для каждого проекта — исходя из его функциональности и сложности.
В нашем случае фиче-модули на основе бизнес-концепций разделены на соответствующие фиче-команды. Но это не общая практика, а лишь пример, как структура кода матчится в структуру команды по Конвею.
Слои полезны тем, что позволяют настраивать правила взаимодействия между модулями. Например, в нашем проекте таких правил несколько:
Модули верхнего уровня знают все о нижних и могут их использовать.
Модули нижнего уровня не знают ничего о верхних.
Модули одного уровня могут знать друг о друге, если предусмотрены прямые или непрямые зависимости, без которых не обойтись. Подробно это правило разберем в будущих статьях про подходы к модуляризации.
После выделения слоев, формирования модулей и их распределения по выделенным слоям можно строить ациклический граф зависимостей между модулями. Это поможет понять принцип взаимодействия модулей и продумать алгоритм их связи с учетом потенциальных ошибок.
На этом же этапе нужно учесть потенциальное масштабирование проекта, увеличение кодовой базы и количества модулей.
Важно понимать, всю описанную подготовку лучше сначала провести «на бумаге». Подход с предварительным проектированием модуляризации поможет лучше понять потоки данных между бизнес-единицами проекта, найти ошибки в их взаимодействии, а также избежать ошибок при проектировании в будущем.
На этом на данный момент у меня всё. В следующих статьях я расскажу о более прикладных вопросах процесса создания modern многомодульного приложения в iOS (критическом пути, Api/Impl, релизном процессе модуля) и опыте команды СберЗдоровья: нашем технологическом стеке, выборе способов линковки и взаимодействии со сторонними зависимостями.
Саммари
Модуляризация — отличный подход, который помогает упростить разработку приложений, их поддержку и развитие. Но это сложный процесс, который требует много времени и зависит от многих факторов, в том числе от структуры компании и ее бизнес-целей.
Предпосылок к модуляризации приложений много — от сложностей в работе с большой системой до необходимости повышения качества разработки. У каждого они свои. Понять, нужна ли в вашем проекте модуляризация приложения, поможет список вопросов-маркеров.
Перед модуляризацией приложения желательно подготовиться. Например, автоматизировать настройку окружения, построить пайплайны, внедрить кодогенерацию.
Перед разработкой желательно все спроектировать «на бумаге» — это поможет избежать большого количества проблем.