Проблема UseCase-ов: что нужно знать разработчикам Android
Введение
Если вы уже определенное время занимаетесь разработкой 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 с умом, и вы будете на правильном пути к написанию чистого и эффективного кода.