В активно развивающихся приложениях порой сложно обеспечивать масштабируемость, удобочитаемость и качество кода. Это частая ситуация, которая со временем может усложнить поддержку, выявление проблем и их устранение. Избежать подобных проблем помогает модуляризация приложений — подход, при котором кодовая база делится на небольшие специализированные, готовые к повторному использованию модули.

Меня зовут Кирилл Смирнов. Я технический лидер 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, релизном процессе модуля) и опыте команды СберЗдоровья: нашем технологическом стеке, выборе способов линковки и взаимодействии со сторонними зависимостями.

Саммари

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

  2. Предпосылок к модуляризации приложений много — от сложностей в работе с большой системой до необходимости повышения качества разработки. У каждого они свои. Понять, нужна ли в вашем проекте модуляризация приложения, поможет список вопросов-маркеров.

  3. Перед модуляризацией приложения желательно подготовиться. Например, автоматизировать настройку окружения, построить пайплайны, внедрить кодогенерацию. 

  4. Перед разработкой желательно все спроектировать «на бумаге» — это поможет избежать большого количества проблем.

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