Что хочешь видеть в новом проекте

Ответ

Чистую архитектуру (Clean Architecture, MVVM/MVI), модульность, Kotlin-first подход.

Обязательно:

  • DI (Hilt/Dagger)
  • Coroutines/Flow
  • Jetpack Compose (если новый проект)
  • Unit/UI тесты (MockK, Espresso)
  • CI/CD (GitHub Actions/Bitrise)

Пример структуры модулей:

:app          // entry point  
:core         // utils, extensions  
:data         // repo, API (Retrofit), DB (Room)  
:domain       // use cases, models  
:features     // feature modules  

Бонус:

  • Dynamic Delivery
  • Multi-module navigation
  • Kotlin DSL для Gradle

Избегать: God-activity, хардкода, legacy-кода без миграции.

Ответ 18+ 🔞

А, ну вот, опять про чистую архитектуру спрашивают. Давайте я вам, как человек, который уже на этом собаку съел (и не одну), на пальцах объясню, чтобы не было как в том анекдоте про «у меня тут всё по SOLID, только I — лишняя».

Смотрите сюда. Вы говорите «чистая архитектура» — я уже чувствую, как у половины читателей глаза стекленеют. «О, опять эти слои, use cases, блядь, овердохуища бойлерплейта». Спокойно. Не надо бздеть. Идея-то проще, чем кажется: твой код должен быть таким, чтобы через полгода, когда продукт-менеджер придет с криком «срочно меняем API провайдера погоды на тот, что с горбатым гномом в логотипе», ты не орал «ёпта, да я тут полгода переписывать буду!», а просто пошел в один модуль :data, нашел там WeatherRemoteDataSource, выкинул один ретрофит-сервис и впендюрил другой. И всё. Без танцев с бубном вокруг всей разъебанной кодобазы.

Вот смотрите на список обязательного. Это не просто модные слова, это ваш щит и меч.

DI (Hilt/Dagger). Без этого — вообще никуда. Это как фундамент. Представьте, вы в каждом экране создаете репозиторий, а в репозитории — датасорс, а в датасорсе — ретрофит с кучей зависимостей. Это пиздец, чувак. Получится спагетти-код, который через месяц сам черт не разберет. Hilt сейчас — это вообще сказка. Аннотацию @HiltAndroidApp в аппликейшен, @AndroidEntryPoint во вьюмодели или активити — и он тебе всё проинжектит. Красота. Главное — не превращай его в магический черный ящик, а понимай, что и откуда берется. А то будет «оно не работает, ёб твою мать!», а ты просто скоуп не тот выбрал.

Coroutines/Flow. AsyncTask и RxJava с их сложностями — это уже в прошлом, как Nokia 3310. Хотя и крутая была зверюга. Корутины — это наше всё. viewModelScope.launch, flow, collectAsStateWithLifecycle в Compose. Это позволяет писать асинхронный код, который выглядит как последовательный. И не надо этих ебаных колбэков на колбэках, где в итоге onSuccess внутри onResponse, который внутри onComplete. Ужас просто. Flow — это стримы данных. Вьюмодель говорит: «у меня тут StateFlow<UiState>», а UI просто на него подписывается и рисует. Идеально.

Jetpack Compose. Если проект новый — даже не думайте. XML с этими бесконечными констрейнтами — это как ехать на Запорожце, когда вокруг уже Теслы. Compose — это декларативный подход. Ты описываешь, как интерфейс должен выглядеть при определенных данных, а система сама решает, что и когда перерисовать. Плюс писать в десять раз быстрее. Да, есть свои грабли (например, с рекомпозицией), но игра стоит свеч. mutableStateOf — твой лучший друг.

Тесты (MockK, Espresso). А вот тут многие начинают делать вид, что пишут, а на деле — // TODO: add tests later. Потом этот later никогда не наступает. И когда что-то ломается, ты сидишь и гадаешь: это в новой фиче косяк или в старой, которую ты зацепил? Unit-тесты на use cases и вьюмодели — это must have. MockK — отличная библиотека для моков в котлине. UI-тесты (хоть они и медленные) — штука хорошая для критичных сценариев. Хоть что-то должно быть покрыто, иначе доверия ебать ноль к своему же коду.

CI/CD (GitHub Actions/Bitrise). Чтобы не было «а у меня на машине работало!». Настроил пайплайн: пулл-реквест создался -> запустились линтеры, юнит-тесты -> если всё ок, можно мержить -> после мержа в main собирается билд для тестировщиков. Автоматизация — она от греха подальше. Руками это делать — смертный грех для разработчика.


Про структуру модулей

Вот эта схема — это не просто так, это и есть суть.

:app          // Точка входа. Только манифест, тема, и запуск навигации. Больше НИ-ЧЕ-ГО.
:core         // Всякая общая хрень: утилиты, extensions, сериализаторы, диспетчеры потоков.
:data         // Вот тут живут данные. Retrofit, Room, DataSource-ы, RepositoryImplementation.
:domain       // Самое сердце. Use cases (интеракторы), модели бизнес-логики, интерфейсы репозиториев.
:features     // Каждая фича — отдельный модуль. `:feature-auth`, `:feature-profile`, `:feature-feed`.

Почему так? Принцип зависимостей. Модули зависят друг от друга строго в одну сторону, как стрелочки: (:app -> :features) -> :domain <- :data

  • :app и :features знают про :domain и :core.
  • :domain не знает НИ-О-КОМ. Он только про бизнес-логику. Чистый Kotlin/Java модуль. Тестируется элементарно.
  • :data знает про :domain (чтобы реализовывать его интерфейсы) и про :core.
  • :core тоже ни от кого не зависит.

Получается, если завтра вы захотите заменить Room на Realm, или Retrofit на Ktor — вы идете только в модуль :data. :domain и :features даже не узнают об этом. Это и есть сила.


Бонусы (для отличников)

  • Dynamic Delivery — когда хочется, чтобы приложение было меньше, а некоторые фичи качались только тем, кому надо. Тот же модуль :feature-cat-gifs можно сделать dynamic feature. Но это уже высший пилотаж, без реальной нужды лучше не лезть — можно и обжечься.
  • Multi-module navigation — навигация между фичами-модулями. Тут либо свой роутер пилить, либо использовать Navigation Component с Deep Links. Главное — не передавать туда-сюда Parcelable-модели из :data, а только простые аргументы или ключи. Иначе связность опять поползет вверх.
  • Kotlin DSL для Gradle — это просто приятно. build.gradle.kts читается в разы лучше, чем этот старенький Groovy с его загадочными синтаксисом. Плюс автокомплит в студии работает. Мелочь, а приятно.

Чего избегать как огня (иначе будет вам хиросима):

  1. God-activity/fragment. Эта срань, в которой 1000 строк кода, обработка кликов, работа с сетью, обновление UI и ещё запись в базу. Это антипаттерн, его надо выжигать калёным железом. Активность/фрагмент/компоузбл — это только отображение и передача событий во вьюмодель. ВСЁ.
  2. Хардкод. Строки, цвета, размеры, ключи для API — всё в ресурсы или константы в :core. "https://api.myapp.com/v1" прямо в коде ретрофита — это прямой путь в ад, когда на препроде будет другой урл.
  3. Legacy-код без миграции. Если тянете старый проект — не надо лепить новую фичу по-старому. Выделяете для неё новый :feature-* модуль и делаете по уму. Постепенно старый код будет вытесняться. Иначе получится франкенштейн, которого все будут бояться.

Короче, суть в чём. Чистая архитектура — это не про то, чтобы сразу написать идеальный код. Это про то, чтобы через полгода, когда придёт новая задача, ты не хватался за голову с криком «какого хуя?», а понимал, в какой именно модуль идти и что менять. Это инвестиция в своё же спокойное будущее. Ну, или в будущее того бедолаги, который будет поддерживать твой код после тебя. Не будь пидарасом шерстяным, делай хорошо сразу.