Рефакторинг мобильного кода: как внедрить чистую архитектуру
апр, 27 2026
Представьте, что ваше приложение растет, и каждая новая кнопка в интерфейсе вызывает «эффект домино»: вы меняете одну строчку в UI, а в итоге падает база данных или ломается авторизация. Знакомо? Это верный признак того, что код превратился в «спагетти», где всё перемешано с всем. Когда зависимости переплетены, любое изменение становится рискованным приключением. Выход из этой ситуации - чистая архитектура, которая позволяет изолировать бизнес-логику от внешних деталей.
Главные цели рефакторинга
Когда мы говорим о рефакторинге мобильного кода, мы не просто «причесываем» названия переменных. Мы решаем конкретные проблемы, которые мешают проекту развиваться:
- Разрыв жестких связей между логикой и фреймворками (например, чтобы Android-компоненты не проникали в расчеты прибыли компании).
- Повышение тестируемости: возможность написать unit-тесты на логику без запуска эмулятора или подключения к реальному серверу.
- Ускорение доставки фич: когда каждый разработчик знает, в каком слое находится нужный код, конфликты при слиянии веток в Git случаются реже.
- Снижение технического долга, который обычно копится при спешке перед релизом.
Анатомия слоев: кто за что отвечает
В чистой архитектуре код организуется в виде концентрических кругов. Главное правило: зависимости направлены строго внутрь. Внешние слои могут использовать внутренние, но не наоборот.
Первый и самый глубокий уровень - это Домен (Domain). Здесь живут сущности и бизнес-правила. Например, если вы пишете приложение для доставки еды, правила «заказ нельзя отменить после начала приготовления» должны находиться именно здесь. Этот слой абсолютно автономен и не зависит ни от одного фреймворка.
Следующий слой - Сценарии использования (Use Cases). Это мост между данными и интерфейсом. Сценарий описывает конкретный шаг пользователя: «Добавить товар в корзину» или «Сменить пароль». Он не знает, откуда пришли данные (из сети или из кэша), он просто говорит: «Дай мне данные и сохрани результат».
Замыкают структуру Адаптеры (Adapters) и внешние интерфейсы. Сюда входят UI-экраны, контроллеры, базы данных и HTTP-клиенты. Если завтра вы решите заменить Room на другую библиотеку для локального хранения, изменения коснутся только этого слоя.
| Слой | Что содержит | Зависит от... | Пример (Android/iOS) |
|---|---|---|---|
| Domain | Сущности, Бизнес-логика | Ни от кого | UserEntity, OrderPriceCalculator |
| Use Cases | Прикладная логика | Domain | GetUserDetailsUseCase, CheckoutOrder |
| Presentation | UI, ViewModels | Use Cases | MainActivity, UserProfileViewModel |
| Data/Infrastructure | API, БД, Кэш | Domain/Use Cases | UserRepositoryImpl, ApiService |
Принцип инверсии зависимостей: секретный соус
Как заставить внешний слой (базу данных) работать с внутренним (бизнес-логикой), не нарушая правило «зависимости только внутрь»? Здесь на помощь приходит Принцип инверсии зависимостей (Dependency Inversion Principle).
Вместо того чтобы слой бизнес-логики напрямую вызывал метод конкретного класса базы данных, он определяет интерфейс (порт). Например: «Мне нужен кто-то, кто умеет сохранять пользователя». А конкретная реализация в слое данных (адаптер) реализует этот интерфейс. В итоге бизнес-логика зависит от абстракции, а не от конкретного кода БД. Это и есть основа гибкости: вы можете подменить реальную базу данных «заглушкой» (mock-объектом) за одну секунду, что делает тесты молниеносными.
Практический рефакторинг: как не сломать всё сразу
Переписывать всё приложение с нуля - плохая идея. Вы потратите месяцы, а в итоге получите систему, которая все равно не работает, потому что требования изменились. Лучше использовать итеративный подход.
Начните с «подготовительного» рефакторинга. Выберите один проблемный класс - например, огромный 3000-строчный ViewModel, который и делает запросы в сеть, и считает налоги, и обновляет UI. Выделите из него логику расчета в отдельный Use Case. Перенесите работу с API в отдельный репозиторий. На этом этапе приложение продолжает работать, но связи становятся прозрачнее.
Если вы используете Kotlin, максимально используйте его возможности. Неизменяемые данные (data classes) и функции высшего порядка помогают создать предсказуемый поток данных. Когда данные текут по цепочке чистых функций, вероятность того, что в случайном месте приложения изменится глобальная переменная и всё рухнет, стремится к нулю.
Ловушки и типичные ошибки
Многие разработчики, пытаясь внедрить чистую архитектуру, создают «архитектуру ради архитектуры». Это когда для простого вывода одной строки текста из БД создается пять классов: Entity, Mapper, Repository, UseCase и ViewModel. Если ваше приложение - это простой фонарик, такая структура только замедлит разработку.
Еще одна частая ошибка - утечка деталей реализации. Например, когда в Use Case пролетает объект Response из библиотеки Retrofit. Это катастрофа. Если вы смените библиотеку сети, вам придется переписывать бизнес-логику. Правильный путь: преобразовать HTTP-ответ в доменную модель (простой POJO/Data класс) в слое данных, и только потом передавать его наверх.
Итоги и дорожная карта
Чистая архитектура - это не догма, а инструмент. Она дает вам право менять детали реализации, не боясь сломать весь проект. Регулярный рефакторинг помогает бороться с техническим долгом и поддерживать скорость разработки даже спустя годы после запуска.
Не слишком ли много шаблонного кода (boilerplate) в чистой архитектуре?
Да, классов становится больше. Но это плата за разделение ответственности. Вам будет проще найти ошибку в маленьком классе UseCase, чем искать её в гигантском файле, где смешаны UI и сетевые запросы. В долгосрочной перспективе время на поддержку сокращается.
Можно ли использовать чистую архитектуру в маленьких проектах?
Можно, но стоит соблюдать баланс. Для MVP или маленького приложения достаточно разделить код на UI и Data. Полноценный слой Use Cases стоит вводить, когда бизнес-логика становится сложнее, чем просто «загрузил из сети - показал на экране».
Чем отличается Clean Architecture от MVVM?
MVVM - это паттерн для слоя презентации (как связать UI и данные). Чистая архитектура - это глобальная стратегия организации всего приложения. Они отлично работают вместе: MVVM управляет тем, как данные отображаются, а Clean Architecture определяет, откуда эти данные берутся и как обрабатываются.
Нужно ли создавать отдельный Use Case для каждого действия?
Желательно. Один Use Case должен решать одну конкретную задачу. Это делает код самодокументированным: просто взглянув на список файлов в папке usecases, новый разработчик поймет все возможности приложения.
Как тестировать такую архитектуру?
Именно здесь проявляется главная сила. Вы пишете Unit-тесты для Domain-слоя, используя моки для интерфейсов репозиториев. Вам не нужен Android-контекст или реальный сервер, поэтому тесты запускаются за миллисекунды.