Рефакторинг мобильного кода: как внедрить чистую архитектуру

Рефакторинг мобильного кода: как внедрить чистую архитектуру апр, 27 2026

Представьте, что ваше приложение растет, и каждая новая кнопка в интерфейсе вызывает «эффект домино»: вы меняете одну строчку в UI, а в итоге падает база данных или ломается авторизация. Знакомо? Это верный признак того, что код превратился в «спагетти», где всё перемешано с всем. Когда зависимости переплетены, любое изменение становится рискованным приключением. Выход из этой ситуации - чистая архитектура, которая позволяет изолировать бизнес-логику от внешних деталей.

Чистая архитектура - это подход к проектированию ПО, при котором код разделен на независимые слои, где внутренние уровни (бизнес-логика) ничего не знают о внешних (UI, БД, API). Это позволяет менять базу данных или переписывать интерфейс, не затрагивая сердце приложения.

Главные цели рефакторинга

Когда мы говорим о рефакторинге мобильного кода, мы не просто «причесываем» названия переменных. Мы решаем конкретные проблемы, которые мешают проекту развиваться:

  • Разрыв жестких связей между логикой и фреймворками (например, чтобы 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-контекст или реальный сервер, поэтому тесты запускаются за миллисекунды.