Разработка корпоративного приложения на Flutter.
Разработка мобильного приложения на Flutter для крупной компании — это совсем не то же самое, что для стартапа или небольшого проекта. Когда я говорю про крупные проекты, я имею в виду такие, где задействованы много команд, компаний и специалистов из разных областей. Тут разработка — это лишь один кусочек большого пазла.
В небольших проектах всё просто: команда обычно малочисленная, все друг друга знают. Например, бэкенд-разработчик, который делает API, может сидеть буквально за соседним столом. Но в крупных проектах всё иначе. Когда нужно настроить сложное взаимодействие между фронтендом и бэкендом, ты не можешь просто подойти к коллеге и обсудить всё за кофе — они могут быть в другом городе или даже стране.
В таких проектах приходится заранее продумывать, как справляться с проблемами, которые не решить "на месте". И это касается не только кода, но и коммуникации. Давай разберём, как можно упростить себе жизнь. Я расскажу про наш опыт разработки больших приложений на Flutter, над которыми мы работали несколько лет с большим количеством разработчиков из разных команд.
В этой статье обсудим:
1. Кто отвечает за код в разработке на Flutter?
2. Как решить проблему с ответственностью за код в крупном проекте на Flutter?
3. Как разделить код в крупном приложении на Flutter?
4. Проблемы, которые будут с самого начала.
5. Кто такие эти технические команды и зачем они нужны?
6. Почему без них никуда?
7. Структура проекта в разработке на Flutter: как лучше организовать код?
а) Горизонтальная структура: слои в приложениях Flutter.
б) Вертикальная структура: функции в разработке на Flutter.
в) Преимущества вертикальной структуры в Flutter.
8. Как это выглядит в приложении Flutter?
9. Как организовать код внутри модуля?
10. Выполнение задач в моно-репозитории.
11. Несколько слов о Melos.
а) Чем полезен Melos в разработке на Flutter
б) Преимущества использования Melos.
в) О параллельном выполнении задач.
12. Коммуникация между подразделениями в разработке мобильного приложения на Flutter.
а) Зависимости между командами: синхронные и асинхронные.
б) Как решить проблему зависимостей?
в) Как сделать взаимодействие проще?
г) Основной принцип работы.
13. Навигация в разработке приложений на Flutter.
а) Как можно организовать навигацию?
б) Отделение целей навигации от реализации страницы.
в) Отделение навигации от Flutter.
г) Почему это важно?
14. Системы управления локализацией и переводом в разработке на Flutter.
а) Как Flutter решает задачу локализации?
б) Проблемы с локализацией и как их решить?
в) Использование системы управления переводами (TMS).
г) Как работает система управления переводами?
д) Почему важно выбрать правильный инструмент для TMS?
15. Автоматические тесты пользовательского интерфейса в разработке на Flutter.
а) Как использовать UI-тесты в команде?
б) Решение проблемы — интеграция UI-тестов в SCRUM.
в) Запуск UI-тестов в процессе разработки.
16. Контракты в разработке приложений на Flutter.
а) Решение: строго типизированные контракты.
б) Преимущества такого подхода.
в) Потенциальные проблемы.
17. Легаси в разработке приложений на Flutter.
а) Как работать с устаревшим кодом?
б) Проблема разбитых окон.
в) Роль технического отдела.
18. Дизайн система.
19. Вместо заключения.
Кто отвечает за код в разработке на Flutter?
Поговорим о том, кто отвечает за код. В небольших проектах всё просто: если команда (одна на весь проект) написала код, то она за него и отвечает. Внутри команды обязанности могут быть распределены по-разному, но внешне всё выглядит однозначно — код принадлежит команде, то есть каждый член команды заинтересован в том, чтобы код не устаревал и все работало исправно.
А что, если проект побольше? Например, представьте проект, над которым работают более 30 разработчиков Flutter, разделённых на 10 бизнес-команд. Можно было бы сказать, что каждая команда отвечает за свой кусок кода. И это в основном так.
Но проблема в том, что все команды работают над одним и тем же приложением и одной кодовой базой. Это значит, что если каждая команда что-то добавляет в один и тот же код, то получается, что за всё отвечает... никто.
В такой ситуации никто не захочет исправлять баги, потому что все будут считать, что это должен сделать кто-то другой. Если нужна новая функция, её придётся писать самостоятельно, потому что никто не будет чувствовать за это ответственность.
Работа в команде эффективна, только когда каждый понимает свою зону ответственности. Если слишком много людей будут отвечать за один и тот же код, то никто не будет чувствовать, что это их обязанность.
Поэтому в крупномасштабных приложениях нужно чётко распределять ответственность и устанавливать правила. Если сделать это в начале проекта, это не будет выглядеть странно или искусственно — все воспримут это как само собой разумеющееся.
Как решить проблему с ответственностью за код в крупном проекте на Flutter?
Прежде всего отмечу, что в проекте на Flutter (да и в любом другом) каждая строка кода принадлежит не одному человеку, а команде. В любом случае - в любом проекте (от мала до велика), даже если в команде один разработчик, ответственность за код всё равно лежит на команде. Почему так? Потому что разработчики редко сами решают, что делать в первую очередь — приоритеты расставляют бизнес-заказчики.
Если за код будет отвечать один человек, то при смене приоритетов или увольнении этого человека поддержка кода сильно затруднится. Но если ответственность лежит на команде, то даже если разработчик уйдёт, оставшиеся в команде продолжат поддерживать код.
Ответственность за код включает в себя не только исправление багов, но и приведение кода в соответствие с новыми стандартами. Однако это не значит, что только одна команда может редактировать код. Если нужно добавить новую функцию, это может сделать другая команда, но ответственность за поддержку всё равно останется за исходной командой.
Как разделить код в крупном приложении на Flutter?
Когда одна команда разрабатывает целое приложение — будь то небольшое мобильное или микросервис на бэкенде — всё просто: они отвечают за весь код, который создают. Может быть, они будут использовать какие-то общие внутренние библиотеки, но их пишет и поддерживает другая команда, так что они даже не видят этого кода, просто пользуются им.
Но в большом приложении на Flutter, над которым одновременно работают много команд, всё гораздо сложнее. Тут все работают с одной кодовой базой, и границы между бизнес-функциями не всегда чёткие.
Можно было бы жёстко разделить зоны ответственности и вообще не пересекаться. Но тогда придётся физически разделить код, а потом как-то всё это соединять, чтобы приложение работало. Это добавит много рутины и простоев в разработке. А нам нужно двигаться быстро.
Можно сделать так: разделить проект на пакеты, как если бы мы физически разделили его между командами, но оставить всё в одном репозитории. Чтобы пакеты не мешали друг другу, можно использовать обычные ссылки на пути в коде.
Что это даёт:
- У каждой команды есть чёткие границы ответственности за свой пакет. Они знают, что отвечает только за свой код.
- Все пакеты остаются в одном репозитории, поэтому можно легко посмотреть код других команд. Никаких закрытых «чёрных ящиков».
- Можно собрать всё приложение сразу с помощью CI. Это значит, что если что-то сломается, об этом сразу узнают все.
Этот подход помогает держать код в порядке, а команды — в курсе изменений.
Конечно, это не идеальное решение, и есть подводные камни. Например, раз код остаётся в одном репозитории, всегда есть риск, что кто-то случайно изменит чужой пакет.
Это случается редко, если в команде есть культура соблюдения правил. Но если у вас постоянно возникают таки�� ситуации, то дело не в структуре кода, а в организационных проблемах. И с этим надо разбираться отдельно.
Проблемы, которые будут с самого начала.
В любом проекте всегда есть «общая» папка — место, где хранится то, чем пользуются все команды, а также локальные пакеты, которые потом собираются в один файл, чтобы приложение заработало.
И вот в чём проблема:
- У этих общих пакетов нет чёткого владельца. Все могут туда что-то добавить, но никто за них не отвечает.
- Полностью отказаться от них нельзя, потому что тогда каждый будет писать одни и те же вещи заново. Это и так иногда происходит, но с общими пакетами это хотя бы случается реже.
- Нужен ещё один пакет — главный, который собирает все части приложения воедино. Без него вообще ничего не заработает, потому что это точка входа в приложение. И в него вносят изменения все команды.
Звучит как хаос, да? Но избежать этого нельзя, нужно искать выход.
Мы поняли, что тут не поможет просто назначить одну из команд владельцем общего кода. Это не сработает, потому что у каждой команды свои задачи и приоритеты.
Зато хорошо работает другой подход — создать отдельную техническую команду. Эта команда отвечает за всё «общее» и за то, чтобы весь код работал вместе. Они как бы «владельцы» кода, который нужен всем, но не принадлежит никому.
Кто такие эти технические команды и зачем они нужны?
Эти команды — не просто группа разработчиков, которым не нашлось другой работы. Они выполняют очень важную роль:
- Они не занимаются созданием функций для пользователей. У них нет «владельца продукта», который бы ставил задачи. Их задачи — это технические вопросы, которые нужны всем, но не относятся напрямую к бизнесу.
- Они отвечают за архитектуру всего приложения. Это значит, что они задают общие правила разработки, чтобы код был аккуратным, понятным и легко поддерживался.
- Они следят за тем, чтобы никто не «ломал» общий код, и решают конфликты между командами, если кто-то вносит изменения, которые могут повлиять на других.
Почему без них никуда?
Представьте, что над одним приложением работают 30 человек. Если не будет тех, кто следит за общими правилами и архитектурой, то получится хаос:
- Каждый будет писать код по-своему, и поддерживать его станет сложно.
- Команды начнут «наступать друг другу на пятки», когда изменения одного сломают работу другого.
- В конце концов приложение просто не соберётся в единое целое.
Можно надеяться, что кто-то сам захочет взять на себя роль лидера и будет следить за порядком. Но это рискованно: если оставить всё на самотёк, то процесс будет долгим и болезненным.
Поэтому нужна команда архитекторов — тех, кто отвечает за общую архитектуру, пишет правила и следит за их выполнением. Они решают, как пакеты взаимодействуют друг с другом, и думают о том, что нужно разработчикам для удобной работы.
Да, они не создают функции, которые видят пользователи. Да, их работа не приносит явных бизнес-результатов. Но без них приложение просто развалится.
Они берут на себя ответственность за общий код, который нужен всем, но не принадлежит никому. Они делают так, чтобы всё работало вместе. Они разрабатывают инструменты и решают технические проблемы, чтобы разработчикам было проще писать код.
И да, иногда у них много работы, потому что каждая мелкая техническая ошибка или необходимость оптимизации — их забота. Если никто не хочет брать на себя ответственность, её берёт техническая команда.
Структура проекта в разработке на Flutter: как лучше организовать код?
Когда мы начинаем новый проект по разработке мобильного приложения на Flutter, сразу возникает вопрос: как лучше всего организовать код, чтобы избежать ошибок прошлого? Существует много подходов и архитектурных шаблонов, которые помогают структурировать код.
Основные подходы делятся на два типа:
- Горизонтальный (разделение по слоям)
- Вертикальный (разделение по функциям)
Давайте разберёмся, что это значит и какой вариант лучше выбрать для разработки приложений на Flutter.
Горизонтальная структура: слои в приложениях Flutter.
Горизонтальный подход предполагает разделение кода на слои, каждый из которых отвечает за свою часть функционала. Обычно эти слои выглядят так:
- UI слой: виджеты и интерфейс Flutter.
- Логика приложения: управление состоянием, обработка пользовательских действий.
- Слой данных: работа с репозиториями и источниками данных (API, базы данных).
На первый взгляд, такая структура выглядит логично и упорядоченно. Она чётко отделяет пользовательский интерфейс от бизнес-логики и данных. Но есть и подводные камни:
- Нет чёткого владельца. Например, кто отвечает за репозитории? Кто следит за виджетами? Вроде бы все, а значит, на практике — никто. Это приводит к хаосу, когда все вносят изменения, но никто не отвечает за результат.
- Трудности в коммуникации. Код начинает отражать структуру команды, о чём говорит закон Конвея: "Любая организация создаёт систему, структура которой повторяет структуру коммуникаций в этой организации".
- Нет связи с бизнес-областями. Мы же не обсуждаем на встречах «слой данных» или «слой виджетов». Мы говорим о конкретных функциях и бизнес-требованиях, например, о кредитных заявках или проверке клиентов.
Вывод: горизонтальная структура не отражает то, как мы работаем и общаемся в реальной жизни. Поэтому в долгосрочной перспективе она приводит к путанице и усложняет сопровождение кода.
Вертикальная структура: функции в разработке на Flutter.
Чтобы лучше организовать код в приложении на Flutter, стоит попробовать вертикальный подход. Он предполагает, что код делится по бизнес-функциям, а не по техническим слоям.
Что это значит на практике?
- Каждая бизнес-функция (например, «кредиты» или «регистрация клиента») — это отдельный модуль или пакет.
- Внутри модуля находятся все уровни — от пользовательского интерфейса до логики и данных, но только те, которые относятся к этой функции.
- Каждый модуль принадлежит конкретной команде. Это убирает размытость в ответственности: если команда работает над кредитами, она отвечает за весь код, связанный с кредитами.
Преимущества вертикальной структуры в Flutter.
- Чёткое распределение ответственности. У каждого модуля есть свой владелец — команда, которая разрабатывает и поддерживает его. Это уменьшает количество конфликтов и улучшает качество кода.
- Соответствие бизнес-логике. Модули отражают реальные бизнес-функции, а не абстрактные технические слои. Это делает код более понятным для всех участников проекта, включая бизнес-аналитиков и продакт-менеджеров.
- Минимум пересечений между командами. Разработчики работают в своих модулях и меньше «наступают друг другу на пятки». Это снижает количество конфликтов при объединении кода.
Как это выглядит в приложении Flutter?
Представьте, что у вас есть приложение для банка с такими функциями:
- Кредиты
- Регистрация клиента (KYC)
- История транзакций
При вертикальной структуре в разработке на Flutter каждая функция будет представлена отдельным пакетом. Например:
Важно помнить:
- Один модуль — одна команда. У каждого пакета должен быть только один владелец, но одна команда может владеть несколькими пакетами. Например, «Команда по работе с клиентами» может отвечать и за модуль кредитов, и за модуль KYC.
- Внутренняя структура предсказуема. Каждый модуль организован одинаково, что помогает новым разработчикам быстро понять, где что находится.
Как организовать код внутри модуля?
В разработке мобильного приложения на Flutter важно сохранить предсказуемость и простоту структуры. Внутри модуля можно использовать тот же вертикальный подход:
- Разделение по функциям. Например, в модуле кредитов будут папки для подачи заявки, расчёта процентов и просмотра истории.
- Внутри функции хранятся все части, относящиеся к этой задаче: страницы, виджеты, состояния, данные.
- Чёрный ящик для других команд. Другие команды видят только интерфейсы (контракты) модуля, а внутренняя реализация остаётся скрытой.
Пример:
Вывод:
Вертикальная структура при разработке на Flutter позволяет:
- Упорядочить код по бизнес-функциям, а не по техническим слоям.
- Чётко распределить ответственность между командами.
- Сократить количество конфликтов и упростить сопровождение кода.
Если вы разрабатываете крупное мобильное приложение на Flutter и хотите, чтобы работа шла быстрее и без лишних проблем, попробуйте использовать вертикальную структуру. Она лучше отражает реальную коммуникацию в команде и помогает организовать код так, как этого требует бизнес.
Выполнение задач в моно-репозитории.
Когда работаешь над крупными проектами на Flutter, очень важно, чтобы повседневные задачи выполнялись просто и согласованно. Представьте: вы хотите запустить тесты только в одном пакете или нужно быстро отформатировать код. Было бы здорово делать это без лишних заморочек, верно? Особенно если работа идёт в среде с несколькими пакетами. Тут на помощь приходят инструменты для выполнения задач, как, например, Make или похожие на него. Идеально, если этот инструмент понимает специфику Dart и Flutter, а также подстраивается под экосистему, с которой вы работаете.
Несколько слов о Melos.
К счастью, такие и��струменты уже существуют, и одним из таких для разработки на Flutter оказался Melos. Официально он описывается как «инструмент для управления проектами Dart с несколькими пакетами». По сути, это как раз то, что нужно для удобной работы в большой команде над сложным проектом.
Чем полезен Melos в разработке на Flutter
Melos предоставляет много удобных возможностей:
- Автоматическое управление версиями и генерация списка изменений. Это экономит время и снижает вероятность ошибок.
- Автоматическая публикация пакетов в pub.dev. Вам не нужно вручную выкладывать каждое обновление.
- Связывание и установка локальных пакетов. Это удобно, если вы разрабатываете несколько связанных библиотек.
- Выполнение одновременных команд в разных пакетах. Например, вы можете одновременно запустить тесты во всех модулях.
- Просмотр списка локальных пакетов и их зависимостей. Это помогает лучше понимать структуру проекта.
Melos отлично подходит для управления популярными пакетами, такими как FlutterFire (Firebase для Flutter), Flame (игровой движок) или плагины Flutter Community Plus. Но главное — он позволяет запускать любые команды в каждом каталоге пакетов и гибко фильтровать, в каких пакетах их выполнять.
Преимущества использования Melos
Это действительно удобно, так как вы можете один раз настроить нужные сценарии и фильтры, написать простую документацию (кстати, файл melos.yaml уже довольно понятный) и облегчить жизнь новым разработчикам в команде. Они быстрее разберутся в проекте и начнут вносить свой вклад.
Кроме того, использование Melos упрощает настройку CI/CD конвейера, потому что многие задачи уже будут описаны в одном месте.
О параллельном выполнении задач.
В Melos есть функция параллельного выполнения скриптов. Звучит круто, но тут есть нюансы. Скрипты Flutter могут не запускаться параллельно, потому что некоторые процессы будут блокировать друг друга. В итоге задачам всё равно придётся "ждать" зав��ршения других. Кроме того, если пакетов в проекте много, вывод команд может выглядеть хаотично — порядок сообщений нарушается, и приходится разбираться, что к чему.
Несмотря на эти нюансы, Melos остаётся отличным инструментом, который значительно упрощает разработку на Flutter, особенно если вы работаете над крупным проектом с множеством пакетов.
Коммуникация между подразделениями в разработке мобильного приложения на Flutter
Когда работаешь над большими проектами, физическое разделение на локальные пакеты может помочь, но это не решает все проблемы. К сожалению, иногда такие разделения создают новые трудности. Мы все понимаем, что в командной разработке всегда придётся работать не только в рамках своих задач, но и взаимодействовать с другими.
Представьте ситуацию в крупной системе, например, в банковском приложении с десятью отделами. Некоторые функции зависят от данных других отделов, а в некоторых случаях обновления в одном отделе могут влиять на актуальность данных в другом. Разработать приложение, где будет 10 абсолютно независимых функций — невозможно.
Возьмём, к примеру, банковские учётные записи — это ключевая функция в приложении, с которой связаны все остальные. Когда вы просматриваете список транзакций, вы должны видеть эти транзакции в контексте конкретной учётной записи. То же самое происходит, когда вы совершаете платёж, который изменяет баланс учётной записи — это тоже влияет на её состояние. Все эти зависимости важно учитывать при разработке на Flutter, иначе приложение будет работать не так, как нужно.
Зависимости между командами: синхронные и асинхронные.
Всё это означает, что между командами всегда будут возникать зависимости, и эти зависимости могут быть не только односторонними. Часто бывает, что две команды должны работать в обоих направлениях. Например, вам нужно получить какие-то данные (это синхронная операция), после чего вы что-то делаете, а затем ожидаете, что кто-то другой обновит эти данные (асинхронно).
Это лишь один из примеров, но таких ситуаций бывает много, и не всегда их легко решить. Например, есть команда, которая разрабатывает базовую функциональность авторизации, которая используется во всех отделах. В то же время, чтобы корректно отображать некоторые настройки, связанные с авторизацией, нужно получить данные о пользователе из учётных записей. И тут возникает циклическая зависимость. Конечно, её можно решить, но поддерживать — уже совсем другая история.
Как решить проблему зависимостей?
Из-за таких ситуаций приходится вводить ограничения. Решение довольно простое: можно разделить коммуникацию между модулями на две основные группы — синхронную и асинхронную.
Синхронная коммуникация нужна, когда срочно нужно получить данные или выполнить какое-то действие "прямо сейчас". Например, при авторизации или запросе списка учётных записей. Это важно, потому что данные должны быть получены немедленно, иначе приложение не будет работать правильно.
Асинхронная коммуникация используется, когда нужно уведомить другие отделы о том, что произошло какое-то событие. Например, когда в отделе платежей происходит транзакция, и это событие нужно передать в отдел работы с клиентами для обновления данных учётных записей. Такие действия могут происходить с задержкой, и это нормально.
Как сделать взаимодействие проще?
Можно добавить несколько более конкретных ограничений, чтобы сделать взаимодействие между командами понятным и управляемым. Например, если вы работаете с синхронными фасадами, данные должны предоставляться как потоки (с помощью BehaviorSubject), чтобы интеграция и автоматическое обновление происходили без проблем.
При этом важно, чтобы при возникновении ошибок автоматически выполнялась повторная попытка. Ошибки не должны быть видны через фасад, потому что будет сложно на них адекватно отреагировать. Иначе несколько команд будут выводить одно и то же сообщение об ошибке, что приведет к путанице.
Если вам не нужны данные, а нужно просто выполнить действие, то вы предоставляете метод, который делает это автономно. Например, метод, который генерирует отчёт или выполняет оплату. В таких случаях правила взаимодействия должны быть минимальными и понятными.
Асинхронные фасады работают по аналогичному принципу, только здесь вы предоставляете событие, которое описывает произошедшее действие. Например, при совершении платежа вы отправляете событие с деталями: какие учётные записи участвовали, сумма, назначение платежа. Никакой дополнительной информации не требуется. При этом нет необходимости хранить события, так как они служат лишь для обновления интерфейса пользователя или перезагрузки данных.
Основной принцип работы
В конечном итоге, всё, что происходит в приложении, выполняется на сервере, а события передаются через систему. Это позволяет поддерживать систему в долгосрочной перспективе и не создавать лишние сложности при разработке мобильного приложения на Flutter.
Таким образом, разделяя зависимости на синхронные и асинхронные, и используя подходы, которые позволяют быстро и удобно обмениваться данными между отделами, можно оптимизировать процесс разработки. Это особенно важно при создании крупных, сложных приложений, где каждое изменение может повлиять на множество других функций.
Навигация в разработке приложений на Flutter
Ещё один способ взаимодействия между отделами — это навигация между разными частями приложения. В таком большом проекте, где, например, задействовано 10 разных отделов, часто нужно переходить со страниц одного отдела на страницы другого.
Например, со страницы, где показывается информация о банковском счёте (это зона ответственности отдела по работе с клиентами), должен быть удобный переход к странице банковских переводов (а это уже зона отдела по платежам).
Как можно организовать навигацию?
Можно создать свою систему навигации, которая будет основана на стандартной навигации Flutter, но с добавлением нескольких важных улучшений, чтобы она подходила для работы нескольких команд. Вот чего можно добиться, разрабатывая такую систему:
- Отделение целей навигации от реализации страницы
- Возможность передавать контекстные данные между страницами
- Изоляция навигации от самой реализации Flutter
Отделение целей навигации от реализации страницы.
Чтобы переходить между разными группами страниц, не раскрывая их внутреннюю реализацию, можно разделить описание страницы на две части: глобально доступную "цель" и закрытый конструктор, который создаёт страницу на основе данных из этой цели.
Цель — это абстракция, которая представляет конкретную страницу в приложении. Это не сама страница, а скорее намерение или запрос на создание этой страницы. Например, цель для страницы с информацией о банковском счёте может включать в себя такие данные, как номер счёта, который нужно отобразить.
В своей реализации можно взять за основу принцип, аналогичный намерениям (Intent) в Android-разработке. Цель — это как бы запрос на создание страницы с определёнными данными, которые необходимы для её отображения. Это позволяет легко навигировать между различными модулями и отделами, не заглядывая в детали, как именно устроена каждая страница.
Цель — это идентификатор, который нужен для навигации. Он должен быть доступен всем командам, которым может понадобиться перейти на эту страницу. Поэтому цели определяются в центральном пакете, отвечающем за навигацию.
Отделение навигации от Flutter.
Важным моментом является то, что навигация - это часть бизнес-логики приложения. Когда мы говорим о бизнес-логике, это обычно означает, что навигация должна быть доступна на уровне логики приложения, например, в блоках или кубах, где обрабатывается основная функциональность. Однако, как правило, бизнес-логика не должна зависеть от специфики фреймворка, такого как Flutter. Можно ограничить эту зависимость в архитектуре приложения, чтобы код бизнес-логики оставался независимым от Flutter.
Для того чтобы инициировать навигацию на уровне представления, необходимо создать механизм, который бы позволял передавать навигационные события из бизнес-логики в представление. Это достигается через создание псевдосостояния или дополнительного потока для обработки событий навигации. Это делается для того, чтобы логика навигации не была привязана непосредственно к Flutter, а могла работать в рамках бизнес-логики.
Почему это важно?
Такой подход позволяет сделать навигацию более гибкой и масштабируемой, особенно когда речь идет о большом проекте, разделённом на несколько частей. Такая система позволяет отделам работать автономно, но при этом обеспечивать возможность перехода между различными страницами без нарушения принципов модульности и разделения ответственности.
Когда навигация изолирована от специфики Flutter, её можно легко интегрировать в бизнес-логику, что значительно упрощает дальнейшую разработку и поддержку приложения. Можно инициировать навигацию прямо из блока или куба, не завися от фреймворка, что делает приложение более универсальным и удобным для командной разработки.
Системы управления локализацией и переводом в разработке на Flutter
Одной из ключевых задач при разработке крупных приложений на Flutter является сделать его доступным для людей из разных культур, стран и регионов. Это означает, что нам нужно настроить локализацию, которая часто обозначается как l10n. Локализация — это процесс адаптации перевода продукта для конкретной страны или региона, чтобы пользователи могли использовать приложение на своём родном языке и с учётом культурных особенностей.
Локализация является частью более широкого процесса, называемого интернационализацией, или i14n. В приложении и��пользуется множество строковых значений, и все эти строки нужно перевести в зависимости от настроек устройства пользователя или его личных предпочтений в приложении.
Как Flutter решает задачу локализации?
У Flutter уже есть готовое решение для локализации. Для этого существуют два ключевых пакета: flutter_localizations (для l10n) и intl (для i14n). Эти пакеты тесно связаны между собой и работают в паре, чтобы сделать приложение доступным для пользователей из разных стран. На практике, самый популярный подход заключается в использовании этих пакетов вместе с .arb файлами, которые содержат пары "ключ-значение" для каждой строки, подлежащей локализации.
Проблемы с локализацией и как их решить?
Одной из сложностей является то, что информация о том, какие строки нужно локализовать и как это сделать, в основном касается не разработчиков, а бизнес-специалистов. Это могут быть маркетологи, владельцы продукта, переводчики и другие сотрудники, которые работают над локализацией контента. Разработчики, как правило, занимаются реализацией технической части, а вопрос локализации — это вопрос продукта.
Таким образом, файлы локализации, хранящиеся в репозиториях кода, не должны быть единственным источником информации для команды. Локализация должна быть управляемой и контролируемой владельцами продукта, а не только разработчиками. Это означает, что необходимо передать ответственность за локализацию и обеспечить эффективную коммуникацию между техническими и бизнес-специалистами.
Использование системы управления переводами (TMS)
Для того чтобы упростить процесс локализации и перевода, существует множество инструментов, называемых системами управления переводами (TMS). Эти инструменты представляют собой веб-приложения, которые оптимизированы для рабочих процессов локализации. Можно использовать Phrase в качестве инструмента TMS, потому что он поддерживает формат .arb и предлагает множество полезных функций, таких как комментарии, отслеживание активности, управление ролями и более сложные рабочие процессы перевода.
Инструменты TMS помогают управлять локализацией и переводами, решая такие задачи, как:
- Отслеживание истории перевода терминов
- Комментарии и обсуждения локализации
- Управление глоссариями терминов, которые могут быть непонятны переводчикам
- Импорт и экспорт ключей и значений из файлов l10n
- Переводы с управлением версиями (например, аналогично Git)
Эти инструменты значительно упрощают работу с локализацией, даже если проект небольшой.
Как работает система управления переводами?
Важно помнить, что процесс перевода — это не то же самое, что процесс разработки. Поэтому для управления локализацией нужны специальные инструменты, которые учитывают особенности перевода. В крупном проекте бизнес-специалисты обычно определяют начальные условия для локализации, а затем передают эти материалы переводчикам. Переводчики, в свою очередь, могут отправлять переводы носителям языка для проверки и уточнений. После этого переводы могут быть приняты или изменены.
Такой процесс требует проверки переведённых терминов и предложений, что может быть сложно без правильного инструмента. Система управления переводами помогает упростить этот процесс, обеспечивая необходимые механизмы для проверки, редактирования и утверждения переводов.
Почему важно выбрать правильный инструмент для TMS?
Перед тем как начать использовать систему управления переводами, важно протестировать её, чтобы убедиться, что она подходит для вашего проекта. Разные инструменты TMS могут иметь различные функции и по-разному работать с форматами файлов, такими как .arb, используемый в Flutter. Поэтому стоит тщательно проверять, что выбранный вами инструмент совместим с процессом разработки на Flutter и правильно работает с файлами локализации.
Это особенно важно, если ваш проект включает поддержку нескольких языков, и вы планируете добавлять новые переводы в будущем. Выбор правильного инструмента TMS позволит упростить управление локализацией и ускорить процесс перевода, сэкономив время и ресурсы в долгосрочной перспективе.
Автоматические тесты пользовательского интерфейса в разработке на Flutter.
Согласитесь, UI-тесты — это отличная вещь. Они позволяют значительно упростить тестирование приложений, снизить количество ошибок и быстро получить обратную связь, если вы случайно что-то сломали. Однако, как бы это ни звучало здорово, поддерживать такие тесты не так то просто. Обычно для этого требуется целая команда, которая будет заниматься исключительно написанием и поддержкой UI-тестов, но это, действительно, того стоит.
Как использовать UI-тесты в команде?
Важно правильно использовать тесты в разработке. Если тестировщики работают отдельно от разработчиков, процесс тестирования становится медленным и неэффективным. Тестировщики начинают писать тесты, не до конца понимая, как работает код и что он должен делать. Из-за этого им приходится тратить время на уточнения у разработчиков. Ведь если этого не делать, тесты получаются нерабочими, а процесс затягивается.
Главная проблема в том, что это отнимает время у разработчиков, которые могли бы потратить его на более полезные задачи. В итоге и тестировщики, и разработчики тратят много времени на подготовку тестов и общение, которого можно было бы избежать, если бы тестирование было частью процесса разработки с самого начала.
Решение проблемы — интеграция UI-тестов в SCRUM.
Единственное решение этой проблемы — сделать тестирование пользовательского интерфейса неотъемлемой частью процесса разработки в команде SCRUM. UI-тесты должны быть частью критериев приемки каждой юзер стори. Важным моментом является то, что все участники команды будут заинтересованы в написании таких тестов: не только разработчики и тестировщики, но и бизнес-аналитики. Если тесты будут разрабатываться параллельно с новыми функциями, это обеспечит их актуальность.
Запуск UI-тестов в процессе разработки.
Также стоит помнить, что UI-тесты — это такие же тесты, как и другие, и к ним нужно подходить с тем же вниманием. Идеально было бы запускать их в рамках обычного рабочего процесса разработки, например, как часть процесса сборки. Конечно, полный запуск UI-тестов при каждой сборке может занять слишком много времени, но хотя бы минимальное подмножество тестов должно запускаться. Остальные тесты могут запускаться периодически, например, несколько раз в день.
Контракты в разработке приложений на Flutter.
Когда мы говорим о контрактах для API, большинство из нас сразу представляет стандартный API, который работает на базе JSON (или иногда XML). Это может быть что-то вроде REST API, REST-ful, REST-ish или даже GraphQL. На самом деле, не так важно, какой именно стиль API вы используете. Главное, что это не так то просто.
Когда речь идет о создании API, предстоит решить множество вопросов: как правильно организовать конечные точки, как настроить взаимодействие между запросами, будет ли сложно составить запросы, и как гарантировать, что данные будут передаваться одинаково в разных запросах. Вроде бы простые вещи, но на практике это довольно непросто. Нужно тщательно продумать каждый аспект, чтобы всё работало корректно.
Да, конечно, есть инструменты, как OpenAPI, которые помогают нам описывать API в структурированном виде. Однако даже с такими инструментами не удается избежать некоторых проблем. Генерация контракта для бэкенда и фронтенда из схемы OpenAPI может приводить к различиям, потому что генераторы для этих двух частей будут работать немного по-разному.
Что ещё усложняет процесс, так это то, что клиент должен работать с API так, как этого требует инструмент. И если инструмент или подход отличаются от того, что нужно, предстоит вручную настроить клиентскую часть. Если нужно реализовать какую-то особенную функцию, которая выходит за рамки стандартных инструментов, скорее всего, придется писать запросы вручную, что приводит к набору трудных для поддержания ручных запросов, которые могут легко сломаться.
Вот почему тестирование контрактов обязательно в процессе разработки. Без надежного набора тестов, которые проверяют контракты, найти причину сбоя в API может быть очень сложно.
Решение: строго типизированные контракты
Чтобы решить все эти проблемы, можно использовать подход строго типизированных контрактов. Эта концепция уже используется в крупных проектах, например, в банковских приложениях, и доказала свою эффективность.
Идея простая: серверные разработчики лучше всех знают, как всё должно работать, потому что они занимаются запросами и бизнес-логикой. Пусть они сами пишут всё, что нужно: и схему запросов, и их обработку, и клиентов для этих запросов. Но вместо того, чтобы использовать OpenAPI, давайте возьмём язык, на котором написана серверная часть.
Серверная часть, скорее всего, написана на универсальном языке. Если ограничиться только самыми важными функциями, то разобрать этот код и создать на его основе клиент будет не так уж сложно.
Преимущества такого подхода
- Один источник информации: Бэкенд-команда решает, как всё должно работать. Конечно, они согласовывают это с мобильными разработчиками.
- Понимание процессов: Бэкенд-разработчики знают, как работают процессы и какие данные нужны. Они могут передать эту информацию клиентам, используя тот же язык. Плюс, в коде есть небольшая документация.
- Меньше лишней работы: Они пишут обычный код, который можно сразу использовать. Это избавляет от необходимости писать документацию, которая всё равно устареет, когда требования изменятся.
- Надёжность: Всё это — код, и он генерируется автоматически. Здесь нет места ошибкам. Если в контракте нужен int, вы не сможете вставить туда String. Если код компилируется (и на сервере, и на клиенте), то, скорее всего, всё работает правильно.
- Удобство работы с клиентом: Клиент — это обычный код на Dart (или JS), который легко найти в проекте. Вы просто открываете папку с API в VSCode или другом редакторе. Вы можете сделать код клиента читаемым, не теряя его функциональности.
- Простое управление версиями: Контракты легко версионировать. Для этого используется git — вы просто указываете конкретный коммит или тег, чтобы использовать нужную версию. Но это не отменяет необходимости версионировать API — это всё равно придётся делать.
Потенциальные проблемы
Но, конечно, этот подход не является волшебной палочкой, и у него есть свои недостатки. Например, если изменяется тип данных в контракте, нужно будет обновить все части кода, которые с ним работают, а это долго и трудоемко. Такие изменения могут затронуть даже те запросы, которые вы не используете напрямую, и вам всё равно придётся адаптировать клиентскую часть.
Кроме того, строго типизированные контракты не решают всех проблем с версиями API. Вы по-прежнему должны работать с ними, но благодаря этому подходу такая работа станет немного проще. Также стоит отметить, что работа с высокоуровневыми типами данных всё равно имеет свои ограничения, ведь, в конечном счёте, это JSON. И все же благодаря строгой типизации можно значительно уменьшить количество ошибок и неопределённости.
Легаси в разработке приложений на Flutter.
Когда мы начинаем работать над крупномасштабным проектом, всегда есть риск, что решения, которые мы принимаем на старте, со временем могут устареть. Это вполне нормально — такие вещи неизбежны. Код, который мы пишем, становится легаси с самого момента его создания. Если бы нам пришлось переписать его с нуля, мы, конечно, учли бы много новых факторов, и сделали бы всё немного по-другому :). Но это не значит, что всё потеряно, и не стоит паниковать. Мы не можем просто удалить старый код, но мы можем найти способ с ним работать.
Как работать с устаревшим кодом?
Первый шаг, который стоит предпринять, — это отказаться от устаревших решений. Иногда мы сталкиваемся с тем, что старые функции, виджеты или классы, которые раньше были актуальны, теперь больше не соответствуют современным требованиям. В этом случае стоит постепенно отказываться от их использования, а на замену предложить что-то более современное, что будет лучше подходить для текущей ситуации. Например, если какие-то методы или классы устарели, в языке Dart есть аннотация @deprecated, которая помогает предупредить других разработчиков о том, что данный элемент больше не следует использовать. Это очень удобно, когда над проектом работает большая команда.
Но важно не только пометить код как устаревший. Нужно реально отказаться от его использования. В противном случае, если старый код продолжает использоваться, а новые решения ещё не внедрены, мы будем иметь два способа решения одной и той же задачи, что только запутает разработчиков и усложнит весь процесс.
Проблема разбитых окон
Ещё одна вещь, о которой стоит помнить, — это так называемое «разбитое окно» в коде. Представьте, что в коде есть явная ошибка или участок, который нуждается в улучшении. Но по какой-то причине никто не решается этим заняться, хотя бы потому, что сейчас нет времени или ресурсов. Это становится «разбитым окном», потому что чем дольше мы игнорируем проблему, тем хуже становится. И вот этот участок кода будет постепенно становиться всё более «грязным», так как никто не будет заниматься его рефакторингом. Это может серьёзно повлиять на проект в долгосрочной перспективе.
Чтобы избежать такого развития событий, важно постоянно следить за кодом, заниматься его улучшением и рефакторингом. Мы всегда должны стремиться минимизировать те концепции и подходы, которые мешают рефакторингу. В частности, это касается устаревших элементов, которые остаются в проекте без должного внимания.
Роль технического отдела.
Для того чтобы поддерживать порядок в работе с устаревшим кодом, нужно организовать взаимодействие между различными частями команды. В крупных организациях за такую работу, как правило, отвечает технический отдел. Он занимается всем, что не связано непосредственно с бизнес-логикой — это CI/CD, общие инструменты, задачи, которые общие для всех команд, и в том числе — отслеживание состояния устаревшего кода. Важно, чтобы технический отдел регулярно проводил совещания по статусу рефакторинга, на которых все разработчики могут обсудить, как идет работа над кодом, что нужно улучшить, какие части необходимо обновить. Это помогает своевременно выявлять слабые места и устранять их.
Очень важно синхронизировать работу разных команд хотя бы раз в неделю. Это помогает отслеживать прогресс и предотвращать накопление технических долгов.
Дизайн система.
Последняя, но не менее важная часть — это подход к дизайну. Дизайн приложения, по сути, это то, как выглядит пользовательский интерфейс (UI) с точки зрения кода. Чтобы создать лучший пользовательский опыт, важно придерживаться единого стиля. Что это значит? Всё должно быть согласованным: от поведения элементов до цветовой палитры и подхода к решению схожих задач в приложении.
Кстати, это не забота разработчиков — они не создают сам дизайн. Они только реализуют идеи и концепции, которые дают дизайнеры. Поэтому важно работать в связке и поддерживать систему дизайна в коде.
Разработчики обязаны использовать компоненты из дизайн-системы. Кстати, эффективность такой системы будет максимальной, если её поддерживает отдельная команда.
Вместо заключения
Всё, о чём мы говорили, — это только небольшая часть того, что нужно учитывать при разработке крупных корпоративных приложений на Flutter. Как и в случае с любым другим фреймворком, когда проект требует высоких стандартов, нужно, чтобы в команде были настоящие профессионалы.
Если вам нужно расширить команду и добавить экспертов по Flutter, мы с радостью поможем! Наши специалисты могут работать напрямую с вашей командой, создавая гибридные группы, чтобы ускорить процесс и добиться поставленных целей.
Посетите наш сайт и оставьте заявку на бесплатную консультацию.
Или можете связаться с нами через телеграмм или вотсапп.