Рефакторинг кодовой базы мобильного приложения
Рефакторинг заказывают не когда «хочется лучше» — а когда новая фича занимает три недели вместо трёх дней, потому что надо переписать четыре экрана, которые тащат за собой один God Object с 2000 строк. Или когда Instruments показывает 30-секундный main thread stall при открытии чата — и никто в команде не берётся его трогать.
Как выглядит кодовая база, которая требует рефакторинга
iOS: ViewController на 1500 строк с URLSession, CoreData, бизнес-логикой и расчётом высоты ячейки в одном файле. NotificationCenter с magic-string именами нотификаций по всему проекту. Closure-hell в completion handler'ах, отсутствие [weak self] = утечки памяти, которые Instruments показывает как стабильный рост Allocations.
Android: Activity с 800 строк, прямые вызовы SharedPreferences в UI-слое, AsyncTask вместо корутин (устарел с API 30), жёсткая зависимость между фичами через статические синглтоны. LiveData и ViewModel подключены, но логика всё равно живёт в Activity/Fragment.
React Native / Flutter: компоненты с бизнес-логикой прямо в JSX/build, состояние через setState на 200+ строчных компонентах без разделения на слои, отсутствие типизации (any везде в TypeScript, dynamic в Dart).
Что делаем и в каком порядке
Рефакторинг без тестов — это переписывание с надеждой, что ничего не сломали. Первый шаг всегда — покрытие критических путей snapshot- и unit-тестами до изменений. Это фиксирует текущее поведение как эталон.
iOS. Выносим сетевой слой в отдельный NetworkService с async/await (Swift Concurrency). ViewController получает только ViewModel через Dependency Injection (без Swinject для простых случаев — достаточно init-инъекции). CoreData operations — в PersistenceController с NSPersistentContainer.performBackgroundTask. Combine или async/await заменяет closure callbacks — читаемость кода меняется кардинально.
Android. Миграция с AsyncTask / RxJava на Kotlin Coroutines + Flow. ViewModel + StateFlow вместо изменяемых LiveData. Repository pattern — UI ничего не знает о том, откуда данные: Room, Retrofit, или кеш. Hilt для DI — устраняет статические синглтоны, которые делают тесты невозможными.
Кейс: Android-приложение для трекинга тренировок, 2 года в production, команда 3 разработчика. Главная проблема: WorkoutActivity.java — 1800 строк, Bluetooth-соединение с датчиком, запись в SQLite, расчёт статистики и UI в одном классе. Новая фича (поддержка второго типа датчика) оценивалась в 6 недель. После рефакторинга: BluetoothSensorManager, WorkoutRepository, StatisticsCalculator, WorkoutViewModel — каждый класс до 200 строк с чёткой ответственностью. Следующая аналогичная фича — 5 дней.
Работа с техническим долгом постепенно
Полный рефакторинг «всё сразу» — почти всегда плохая идея. Работаем по Boy Scout Rule: каждый PR улучшает код, которого касается. Для масштабного рефакторинга — feature branch с поэкранной декомпозицией, параллельный запуск старого и нового кода через feature flag, постепенный перевод трафика.
Метрики результата
Не «стало лучше», а конкретные числа:
- Build time (модульность ускоряет инкрементальную сборку)
- Среднее время добавления новой фичи (velocity из Jira/Linear)
- Количество крешей (Firebase Crashlytics, до и после)
- Test coverage (Istanbul/JaCoCo)
Сроки: рефакторинг одного модуля / экрана — 1–2 недели. Полная архитектурная реструктуризация приложения — 6–14 недель в зависимости от объёма кода и наличия тестов.







