К 2023 году на словах Clean Architecture меня уже почему-то начинает трясти 😱. В основном, конечно, это следствие того как они превратились в карго культ для большой массы людей, к самому анклбобу у меня не так много вопросов вопросов. На днях в одном из андроид каналов взгляд зацепился за репозиторий от автора книги Clean Architecture for Android (не читал, может норм, кто знает, звучит хайпово), где он на котлине и со всеми современными подходами слэш базвордами пытается нам показать как эти ваши андроид приложения надо писать. Интересно, подумал я, контент.
В readme нас сходу встречает святая троица scalability, testability и flexibility. Звучит сильно, не поспоришь, всё нужно. В приложении 2 экрана. Один показывает ответ от запроса на бэк, второй показывает список из локальной базы. Как думаете сколько кода? Правильно, 22 gradle модуля, 7+ тысяч строк котлин кода. Ну да ладно, это на самом деле классический приём, пишешь очень простой проект, представляя что пишешь очень сложный проект, где всё это нужно. Сам так сто раз делал, был бы смысл.
Самое печальное в таких репозиториях всегда в том, что осознанно или нет, важнейшие решения обходятся какими-то микрокостылями или вообще игнорируются. Ты одной рукой для проекта такого масштаба оверинжиниришь, другой рукой втыкаешь какой-нибудь костыль, который на проекте такого масштаба абсолютно некритичен. Нормальное же приложение, если это не разрулить - начнёт трещать по швам.
К самой "чистой архитектуре" придраться довольно сложно, ну есть какие-то слои там: data, domain, **presentation **, стрелочки в правильные стороны. Но вот к реализации именно в виде андроид приложения я бы придрался. Начнём с простого, мы сделали два десятка модулей, наверно от этого какие-то бенефиты по сборке должны быть, да? Нет, зависят друг от друга они достаточно хаотично. Фичи не зависят друг от друга ни в какую сторону, а жаль, это тот момент где твоя модульная архитектура вообще на прочность проверяется. Там появляются подходы типа api/impl модулей, чтобы сборку тяжёлых impl как-то распараллелить, циклические зависимости разруливать и много чего ещё. Тут такого нет. Scalability и Flexibility под угрозой. Так что в текущем виде от многомодульности одни минусы, кроме compile-time проверки что никто нашу архитектуру не нарушил. Если бы это всё было по пакетам разбито в одном модуле, то собиралось бы быстрее, как минимум.
Менеджмент Gradle модулей вообще мягко говоря так себе. Когда ты версию джавы или compileSdk копипастой
растаскиваешь по десяткам модулям, то никакой расширяемостью или гибкостью здесь вообще не пахнет. Особого внимания от
меня заслужил settings.gradle
, который понятную структуру модулей совпадающую с файловой системы умудрился превратить
в какую-то слабо поддающуюся пониманию кашу. Попробуй разберись где в проекте модули, а где просто какие-то папки,
внутри которых модули. Даже андроид студия с ума сходит и иконки не показывает в навигаторе.
Автор на одном экране использует Compose, на втором Fragment с RecyclerView - это самое интересное в этом приложении, если честно, настоящая жизнь обычно так и выглядит. Но то место, где фрагмент в Compose заворачивается выглядит прям очень плохо, почти уверен что развалится, там через рефлексию в кишках FragmentManager какой-то метод приватный вызывается, о чём тут говорить. RecyclerView без каких-нибудь обёрток над ячейками типа AdapterDelegates скейлится плохо сам по себе. Тот экран, который на композе - работает с ViewModel, которая лежит в общей Activity. Так что, по такой логике, добавляя экраны у нас количество полей с вьюмоделями в Activity будет расти, не говоря уж о том, что Activity-контейнер знает про реализацию конкретных фич, что не очень то и чисто. Зачем использовать Hilt мне вообще не понять, существует же Anvil если уж так хочется затащить к себе какую-нибудь DI магию. Hilt и многомодульность вещи несовместимые. Почти все зависимости проекта живут в Singleton скоупе и все Dagger модули написаны в app, что расширяемости проекта тоже особо не помогает. Jetpack Navigation для Compose уже на двух экранах почему-то выглядит как говнокод, я не знаю что с ним не так 😒
Дальше больше. Повсюду Base классы с дженериками, BaseFragment, BaseViewModel, базовые UseCase и ещё много всего. Не видел ни разу чтобы Base классы кто-то нормально написал, они рано или поздно превращаются в помойку. Почему-то базовые классы - это всегда куча новых абстрактных методов, которые нужно переопределить, общеизвестные методы ЖЦ спрятаны и куча каких-то полей, которые пытаются предугадать, что из всего многообразия экранов будет использоваться. Спойлер - угадать ни у кого не получается, появляются какие-то заглушки дженериков, возвращающие Unit методы в каждом классе, и так далее, ну не говоря уж о том, что быстро воткнуть какую-то логику в общий базовый класс - это слишком большой соблазн, мимо которого большинство разработчиков пройти не может. BaseViewModel у автора, кстати, от ViewModel не наследуется, поэтому это просто синглтон на уровне фрагмента. Звучит надёжно как швейцарские часы. 👌
Мой любимый момент здесь - это то что suspend функция execute в UseCase дёргает колбэк, а не просто возвращает какое-то значение, как это suspend функции обычно делают. Это буквально то, о чём я говорю - базовый класс написан таким образом, чтобы удовлетворять всем реализациям (да, там несколько типов юзкейса, которые наследуются от интерфейса юзкейса) вместо того, чтобы удовлетворять здравому смыслу. Лучше было бы если бы базового класса просто не было.
Выводы какие - это хорошо разбитый по слоям, но такой себе проект. Так писать проекты не надо, это плохой образец для подражания. Если у человека есть своя книга и гитхаб выглядит как у сына маминой подруги, то это не значит что ему надо верить, no offense. Посмотрите как делать не надо.