Следуя «Золотому пути», мы не просто пишем код — мы создаем устойчивую и адаптируемую культуру разработки, которая выдержит испытание временем.
Поиск золотого пути
В крупных и корпоративных командах Android-разработчиков поиск оптимального рабочего процесса или «золотого пути» для вашей команды может показаться труднодостижимой целью. В течение последних трех лет мы в Fiverr много размышляли над тем, что именно означает этот термин.
Для нас это лучшие практики, архитектуры и соглашения, которые обеспечивают последовательность и качество вашей кодовой базы и создают устойчивую культуру разработки.
Этот путь — не просто следование тенденциям отрасли или принятие последних buzz-слов, это определение целостного и эффективного рабочего процесса, учитывающего уникальные потребности нашей команды, и принятие сложных решений, особенно при работе с устаревшим кодом.
Цель — создать такую среду разработки, в которой каждый член команды, от джуниоров до лидов, может вносить эффективный вклад, а кодовая база остается надежной и адаптируемой к будущим задачам. В этом посте я расскажу о трудностях, с которыми мы столкнулись, о дилеммах, которые выбирали, и об извлеченных уроках, подчеркивающих сложность баланса между инновациями и реальностью существующего кода в нашем стремлении к «Золотому пути».
Определяя наш Золотой путь
Архитектура: CLEAN основа
Одним из наиболее важных компонентов нашего «Золотого пути» является архитектура, на которой мы строим свою работу. После всестороннего рассмотрения и долгих размышлений мы остановили свой выбор на архитектуре CLEAN. CLEAN (аббревиатура от Composable, Layered, Event-driven, Asynchronous, and Non-blocking) обеспечивает структурированный подход, который разделяет проблемы и гарантирует, что бизнес-логика отделена от слоя пользовательского интерфейса. Такое разделение делает наш код более модульным, тестируемым, простым в сопровождении и адаптируемым к изменениям.
Архитектура CLEAN построена на нескольких ключевых принципах:
- Слой пользовательского интерфейса: Именно здесь пользователь взаимодействует с приложением. Строгое следование паттерну MVVM (Model-View-ViewModel) гарантирует, что пользовательский интерфейс будет максимально без состояний, а вся бизнес-логика будет делегирована ViewModel. UI взаимодействует с ViewModel, передавая события (используя публичные функции) и собирая события, отправленные из ViewModel, с помощью StateFlow и ShareFlow.
- Доменный уровень: Здесь располагается основная бизнес-логика, не зависящая от пользовательского интерфейса и источников данных. Она состоит из чистых функций Kotlin, что делает ее легко тестируемой и многократно используемой в разных частях приложения.
- Слой данных: Слой, отвечающий за работу с данными, полученными из сети, базы данных или других источников. Мы используем паттерны Репозитория, чтобы абстрагировать эти источники данных, гарантируя, что слой домена не будет знать о фактическом происхождении данных. Мы всегда разделяем эту абстракцию на удаленные (REST, GraphQL и т. д.) и локальные (Database, DataStore и т. д.) источники данных, каждый из которых имеет свою ответственность и управляется соответствующим репозиторием. Каждый из наших источников данных находится в отдельном модуле и подключается только по необходимости.
Хотя на бумаге такая архитектура кажется идеальной, модульный подход не только делает кодовую базу масштабируемой, но и способствует многократному использованию, поскольку каждый слой можно разрабатывать, тестировать и поддерживать независимо (особенно после того, как мы начали регулярно внедрять модульное тестирование).
Внедрение этого подхода в проект с существующим legacy кодом поставило перед нами несколько задач.
Проблемы с унаследованным кодом: когда рефакторить, а когда обогащать
Интеграция CLEAN архитектуры в нашу 13-летнюю кодовую базу, которая развивалась в течение многих лет мутаций, сопровождения и подходов разных членов команды, поставила нас перед серьезной дилеммой: когда мы должны были полностью рефакторить старый код, а когда — строить поверх него, откладывая полный рефакторинг?
- Доводы в пользу полного рефакторинга: Полный рефакторинг устаревшего кода для приведения его в соответствие с архитектурой CLEAN дает долгосрочные преимущества. Он устраняет технический долг, делает кодовую базу более согласованной и упрощает будущую разработку. Однако полный рефакторинг требует много ресурсов, времени и связан с риском появления новых ошибок. Решение о проведении полного рефакторинга никогда не бывает простым, особенно когда сроки поджимают, а существующий код все еще функционирует.
- Довод в пользу обогащения: В качестве альтернативы мы рассматривали возможность обогащения существующего устаревшего кода новыми модулями, соответствующими требованиям CLEAN. Такой подход позволил бы нам постепенно внедрять лучшие практики, сохраняя функциональность существующего кода. Однако такая гибридная модель рискует создать фрагментированную кодовую базу, в которой сосуществуют старые и новые соглашения, что может привести к путанице и дальнейшему техническому долгу.
В конечном итоге решение сводилось к конкретному контексту каждой новой функции. В некоторых случаях, когда унаследованный код был относительно стабильным и хорошо понятным, мы предпочли не рефакторить, а обогатить его. В других случаях, когда код был хрупким и трудно расширяемым, мы выбирали полный рефакторинг, несмотря на непосредственные затраты.
Важность рефакторинга кода: избежание ловушки наследия
Одним из ключевых уроков нашего путешествия стала критическая важность регулярного рефакторинга кода. Код, который никогда не подвергается рефакторингу, неизбежно становится устаревшим, независимо от того, насколько хорошо он был написан изначально. Со временем даже самый элегантный код может стать трудным для работы, поскольку требования меняются, зависимости меняются, и появляются новые технологии.
Рефакторинг — это не просто техническое упражнение, это культурное обязательство. Встраивая регулярный рефакторинг в наш цикл разработки, мы обеспечиваем адаптивность и поддерживаемость нашей кодовой базы. Это помогает нам избежать ловушки наследия — когда код становится настолько укоренившимся и хрупким, что любое изменение рискует сломать всю систему.
Кодовые конвенции: поддержание согласованности и качества
Последовательность в программировании так же важна, как и сама архитектура. Хорошо спроектированное приложение может стать кошмаром в обслуживании, если не придерживаться соглашений по кодированию, постоянно пересматривая и развивая их.
Думайте модульно
Модуляризация — еще один ключевой аспект нашей архитектуры и соглашений. Разбивая кодовую базу на небольшие, самодостаточные модули (и подмодули), мы добиваемся лучшего разделения проблем и делаем проект более управляемым. Каждый модуль отвечает за определенную функцию, а подмодуль — за подфункции каждой функции, и может разрабатываться, тестироваться и поддерживаться независимо. Модулизация также улучшает время сборки, поскольку изменения в одном модуле не требуют перекомпиляции всей кодовой базы. Это позволяет ускорить итерации и повысить эффективность циклов разработки.
Внедрение инъекции зависимостей
Инъекция зависимостей (DI) имеет решающее значение для написания тестируемого и масштабируемого кода. Мы выбрали Koin за его простоту и бесшовную интеграцию с Kotlin. Легкость и простота использования Koin идеально подходят для наших нужд, позволяя нам внедрять зависимости без шаблонного кода, обычно связанного с DI-фреймворками.
Отказ от базовых классов и наследования
Хотя наследование является мощным инструментом объектно-ориентированного программирования, оно может привести к созданию жестких иерархий, которые сложно модифицировать. Вместо этого мы перешли на делегаты и расширения. Эти возможности Kotlin позволяют нам писать более гибкий и многократно используемый код, отделяя функциональные возможности от иерархии классов.
Унифицируйте дизайн-систему
Мы разработали комплексную систему дизайна, вдохновленную Material Design, чтобы упорядочить наши UI/UX-процессы. Это позволяет разработчикам быстро внедрять новые функции, обеспечивая при этом целостный внешний вид и восприятие всего приложения.
Мы уделяем особое внимание созданию многократно используемых компонентов, которые повышают визуальную согласованность и удобство использования, а также обеспечивают гибкость для будущих итераций дизайна.
Переходите на Jetpack Compose
Наш переход на Jetpack Compose был обусловлен потребностью в более декларативном и эффективном UI-фреймворке. Compose упрощает разработку пользовательского интерфейса, избавляя нас от шаблонного кода, а его бесшовная интеграция с существующими компонентами архитектуры Android повышает скорость разработки.
Jetpack Compose позволяет нам писать пользовательские интерфейсы с меньшим количеством кода и более интуитивно понятным способом. Этот переход позволил нашей команде ускорить итерации, сохраняя при этом соответствие развивающимся лучшим практикам Android.
Зачем искать этот Золотой путь?
Мотивация, лежащая в основе определения «Золотого пути», выходит за рамки простого технического совершенства. Речь идет о создании общего понимания среди членов команды, снижении когнитивной нагрузки и повышении эффективности процесса разработки.
Вот некоторые из преимуществ, которые мы наблюдаем:
- Сокращение времени на онбординг: Новые члены команды могут быстро освоиться с нашим кодом, поскольку структура и соглашения стремятся к единообразию во всех кодовых базах.
- Более легкие code review: Благодаря стандартизированному подходу обзоры кода больше направлены на улучшение логики и меньше на придирки к стилю или структуре.
- Масштабируемость: По мере роста проекта модульная архитектура позволяет нам эффективно масштабироваться, не переделывая всю кодовую базу.
- Тестируемость: Разделение компонентов и строгое соблюдение слоев делают нашу кодовую базу хорошо тестируемой, уменьшая количество ошибок и повышая доверие к нашим релизам.
Преодоление трудностей: путь вперед
Конечно, определение «Золотого пути» и следование ему не обходится без трудностей. Одним из главных препятствий, с которым мы столкнулись, было сопротивление изменениям, особенно при отказе от привычных, но небезупречных практик, таких как базовые классы. Мы подчеркнули долгосрочные преимущества и предоставили обширную документацию и обучение, чтобы преодолеть это.
Другая проблема заключалась в том, чтобы новая архитектура и соглашения были не просто теоретическими идеалами, а практичными и реализуемыми в нашей повседневной работе. Это потребовало итеративных доработок, контуров обратной связи и, самое главное, готовности адаптироваться по мере того, как мы узнавали, что работает, а что нет.
Путешествие продолжается
Определение «Золотого пути» для нашей команды Android-разработчиков стало преобразующим опытом. Он прояснил наши процессы, повысил качество кода и укрепил способность нашей команды решать будущие задачи. Однако этот путь не является статичным. По мере роста нашей команды и развития технологий мы должны оставаться открытыми для совершенствования нашего подхода.
Борьба с унаследованным кодом и дилеммы, связанные с рефакторингом, преподали нам ценные уроки. Главный из них — понимание того, что любой код, независимо от того, насколько хорошо он написан, в конечном итоге потребует рефакторинга. Приняв эту реальность и внедрив ее в нашу культуру разработки, мы сможем избежать ловушек устаревшего кода и поддерживать надежную, масштабируемую и готовую к новым свершениям кодовую базу.
Главный вывод: каждая команда должна стремиться определить свой собственный «Золотой путь» — тот, который соответствует ее уникальным целям, сильным сторонам и проблемам. Таким образом, мы сможем создать среду разработки, которая не только будет создавать высококачественное программное обеспечение, но и способствовать росту, сотрудничеству и инновациям внутри команды.
В конечном итоге «Золотой путь» — это не просто техническое совершенство, это формирование культуры разработки, в которой приоритетом являются адаптивность, сотрудничество и постоянное совершенствование. И это путь, который стоит пройти.
А что вы считаете наиболее важным при планировании своего оптимального пути?