Рефакторинг кодової бази мобільного додатку
Рефакторинг замовляють не коли «хотілось би краще» — а коли додавання нової функції займає три тижні замість трьох днів, тому що чотири екрани залежать від одного 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 замість coroutines (застаріло з 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 (звичайна init інієкція достатня для простих випадків—Swinject не потрібен). CoreData операції—перемістити в 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, поступове переведення трафіку.
Метрики результату
Не «стало краще», а конкретні цифри:
- Час збірки (модульність прискорює інкрементальну збірку)
- Середній час додавання нової функції (velocity з Jira/Linear)
- Кількість крахів (Firebase Crashlytics, до та після)
- Покриття тестами (Istanbul/JaCoCo)
Час реалізації: рефакторинг одного модуля / екрана — 1–2 тижні. Повна архітектурна реструктуризація додатку — 6–14 тижнів залежно від розміру кодової бази та наявності тестів.







