Введение
Если вы уже определенное время занимаетесь разработкой Android, вы, вероятно, слышали о UseCases. Их часто представляют как Святой Грааль Clean architecture. UseCases призваны отделить бизнес-логику от Presentation и Data слоев, сделав ваш код более модульным, переиспользуемым и тестируемым. Но вот в чем загвоздка: UseCases не всегда являются серебряной пулей. На самом деле, слепое их применение может привести к раздутому коду и ненужной сложности, чего как раз и пытается избежать Clean Architecture. В этой статье мы развенчаем миф о UseCases и обсудим, когда они необходимы, а когда - просто пустая трата времени. Если вы разработчик Android и задаетесь вопросом, приносите ли вы больше вреда, чем пользы, следуя этому шаблону, эта статья для вас.
Спорная правда о UseCases
Теоретически UseCases имеют смысл в Clean Architecture: они инкапсулируют бизнес-логику и гарантируют, что слои вашего приложения остаются разъединенными. Однако реальность в повседневной разработке приложений гораздо более тонкая.
Слишком много разработчиков относятся к UseCases как к требованию, что приводит к избыточному и ненужному коду. Результат? Вместо того чтобы сделать свои приложения более удобными для поддержки и масштабируемыми, они создают раздутые кодовые базы, заполненные слоями абстракции, которые не служат реальной цели.
Когда следует избегать UseCases: будь проще
Давайте начнем с того, когда следует избегать UseCases. Самая большая ошибка, которую допускают многие разработчики, - это использование UseCases для каждого небольшого действия в приложении, даже если не задействована значительная бизнес-логика.
Пример: Получение данных из Repository
Представьте, что вы создаете простое приложение для списка дел, в котором вам нужно получить список задач из локальной базы данных. Действие простое: вы запрашиваете Repository, получаете список и отображаете его в пользовательском интерфейсе.
Использование UseCase здесь только добавит ненужной сложности. У вас нет сложной бизнес-логики, которую нужно изолировать или повторно использовать в разных частях приложения. ViewModel может напрямую вызывать репозиторий без привлечения UseCase.
class MyViewModel(private val repository: TaskRepository) : ViewModel() {
val tasks = liveData {
emit(repository.getTasks())
}
}
Почему здесь избегают UseCase? Потому что он не добавляет ценности. Логика уже проста, и введение UseCase только раздует код без какой-либо очевидной выгоды. Clean Architecture выступает за простоту и ясность, а здесь UseCase сделает обратное.
Когда необходим UseCase: обработка сложной бизнес-логики
Теперь давайте поговорим о том, когда UseCase действительно необходим. UseCases стоит использовать, когда вам нужно инкапсулировать сложную бизнес-логику, которую нужно отделить от Presentation и Data слоев.
Пример: бронирование рейса в приложении для путешествий
Рассмотрим сценарий, в котором пользователь бронирует рейс в туристическом приложении. Этот процесс включает несколько шагов:
Проверка введенных пользователем данных (даты, пункты назначения).
Проверка доступности рейса
Бронирование рейса
Обработка платежа
Отправка подтверждения бронирования
Здесь вы имеете дело с несколькими UseCases между различными службами - доступность рейса, бронирование и оплата - каждое из которых может выдать ошибку.
UseCase в этом сценарии имеет смысл. Инкапсулируя процесс бронирования в UseCase, вы можете:
Обеспечить соблюдение всех бизнес-правил
Повторно использовать логику в разных частях приложения (например, бронирование через разные UI)
Упростить тестирование логики бронирования независимо от UI и уровней данных
class BookFlightUseCase(
private val flightRepository: FlightRepository,
private val paymentProcessor: PaymentProcessor
) {
suspend operator fun invoke(flightId: String, userDetails: User, paymentInfo: PaymentInfo): BookingResult {
if (!validateInput(userDetails)) throw InvalidInputException()
val availability = flightRepository.checkAvailability(flightId)
if (!availability) throw FlightNotAvailableException()
val reservation = flightRepository.reserveFlight(flightId, userDetails)
val paymentResult = paymentProcessor.processPayment(paymentInfo)
return BookingResult.Success(reservation, paymentResult)
}
}
В этом случае UseCase улучшает организацию кода, тестируемость и повторное использование. Без UseCase эта логика, скорее всего, окажется в ViewModel или Activity, что усложнит ее поддержку, тестирование и повторное использование.
Настоящее предназначение UseCases: избегание раздувания, а не его создание
Цель Clean Architecture - не создавать больше слоев абстракции ради самого процесса, а поддерживать вашу кодовую базу чистой, организованной и простой в обслуживании. Введение UseCases там, где это не нужно, нарушает этот принцип.
Вот где ошибаются многие разработчики: они следуют шаблонам, таким как UseCases, потому что им говорят, что это часть Clean Architecture, не понимая полностью, зачем они это используют. В результате их код становится загроможденным UseCases, которые не служат никакой реальной цели, превращая Clean Architecture в то, чего она должна предотвращать — раздутый и сложный в обслуживании код.
Вывод: UseCases — это инструменты, а не правила
Главный вывод: UseCases — это инструменты, а не правила. То, что Clean Architecture предполагает их, не означает, что их следует применять везде. Чрезмерное использование UseCases, особенно в ситуациях без сложной бизнес-логики, приводит к ненужной абстракции и раздутому коду.
Принимая решение о том, внедрять ли UseCases, спросите себя:
Есть ли бизнес-логика, которую нужно отделить от уровня представления?
Будет ли эта логика повторно использоваться в другом месте приложения?
Нужно ли проводить независимое тестирование этой логики?
Если ответ на эти вопросы — нет, пропустите UseCases и сделайте свой код простым. Если ответ — да, то UseCases поможет вам получить более чистую и более поддерживаемую кодовую базу.
Используйте UseCases с умом, и вы будете на правильном пути к написанию чистого и эффективного кода.
Комментарии (12)
MagDen
24.09.2024 08:55Не люблю юзкейсы - конструктор класса возвращает совсем иной объект. Глаза режет)
А добавьте туда высокие требования к ясности названий как классов, так и получаемых переменных. Тут и без юзкейсов зачастую беда с неймингами. А тут смотришь на их применение в коде и в голове крутится - "Что ты такое?"
kavaynya
24.09.2024 08:55+2Что-то вы путаете. Как ни крути, но конструктор класс по прежнему возвращает instance этого класса. А вот уже сам instance можно вызвать после этого с другими параметрами.
MagDen
24.09.2024 08:55Смотри, попутал на ночь глядя)
С конструктором все ок, но все равно выглядит странно. Не могу привыкнуть к ним. К тому же, те примеры, что я видел на практике были не совсем удачные, в отличии от того что тут приведен
kavaynya
24.09.2024 08:55Согласен, что выглядит странно, особенно если так написать )):
SomeUseCase(someRepository)(someId)
Но можно же и явно вызвать метод
invoke
Боюсь с этим мало что можно сделать, современные тенденции, они такие )
clint_eastwood Автор
24.09.2024 08:55в контексте юзкейсов думаю это оправданно. такой подход подталкивает пооектировать их только с одним публичным методом. и странно конда имя useCase совпадает именем метода.
плюс в реальном мире врят ли увидишь приведенную запись. так как скорее всего будет какойто di
kavaynya
24.09.2024 08:55странно конда имя useCase совпадает именем метода.
Что имеется в виду? Не понятно...
Gizcer
24.09.2024 08:55Как сложно =) На самом деле если вы используете иньекцию зависимостей, то в вашу VM придёт переменная someUseCase: SomeUseCase. А потом вы ее вызовите как будто бы обычный метод someUseCase(someID). И выглядит совсем не страшно) Ну и invoke если уж совсем явно хотим.
alyxe
24.09.2024 08:55+1Использование use case везде — это вынужденная мера для сохранения единого стиля, консистентности и архитектуры. Если никто не запретит использовать repository напрямую, то на одном экране будут использованы use case, на другом repository во view model. А на третьем, однажды, вообще решат, что view model — ни к чему, можно запросы дергать из UI кода. Архитектура — есть архитектура. Рассуждения о том, что где-то лишнее приводит лишь к халиварам.
Если вопрос в том, что для простого приложения, по типу todo list, не нужно использовать use case, то соглашусь. MVP Todo list не предполагает логики кроме получения списка и добавления новой записи. Но позже, вероятно, может потребоваться расширение. В таком случае, для выстраивания архитектуры может быть необходимость какой-то код перенести в use case. А для того, чтобы код был идентичен, придется везде мигрировать на use case.
KarmanovichDev
Спасибо за статью!
Полностью согласен! Если UseCase является прокси объектом к другому механизму(репо например) или содержит минимальное кол-ло логики, то выделение UseCase приводит лишь к дополнительным тестам, а учитывая, что репо это внепроцессная зависимость, то по-хорошему и к дополнительным тестовым дублёрам(моки, фейки и т. п.).