Ви інтегруєте новий SDK, збірка падає з linker command failed. Причина — CocoaPod, залишений кілька мажорних версій тому, який тягне застарілий бінарник. Або Android-збірка ламається через конфлікт залежностей Gradle. Кожен такий інцидент — наслідок технічного боргу, накопиченого заради прискорення перших релізів. Ціна цього боргу: зростання time-to-market нових фіч на 40% і збільшення кількості крэшів на 25%. Ми системно вирішуємо проблему технічного боргу в мобільних застосунках: перепроєктуємо архітектуру так, щоб кожна нова фіча не збільшувала борг. Наш підхід дозволяє знизити вартість підтримки на 30% вже через три місяці. Ми провели аудит та рефакторинг понад 40 мобільних проєктів під iOS, Android, Flutter та React Native. Типові знахідки: retain cycles у Swift, витоки пам'яті через LeakCanary, дублюючі HTTP-пакети у Flutter. Кожен другий проєкт мав SQALE-індекс нижче 40, що вимагало негайного втручання. Замовте аудит та отримайте дорожню карту усунення боргу.
Три типи технічного боргу
Три типи боргу, кожен зі своєю ціною:
- Інструментальний. Deprecated API, застарілі SDK, підтримка версій iOS, які App Store вже не приймає. Xcode видає 200 warnings при кожній збірці — команда навчилася ігнорувати їх усі, включаючи ті, які попереджають про реальні проблеми. Інструментальний борг вимагає негайного виправлення, інакше App Store reject — його усунення в 5 разів дешевше, ніж очікування блокування.
- Архітектурний. Відсутність розділення шарів, прямі залежності між фічами, тести написати неможливо без підняття всього застосунку цілком. Вартість кожної нової фічі зростає нелінійно.
- Продуктивнісний. Memory leaks у довгоживучих об'єктах, main thread stall при відкритті екранів, excessive CPU usage у background tasks, що викликає thermal throttling. Користувач помічає, ставить 1 зірку.
Як пріоритезувати технічний борг?
Не все потрібно чинити. Інструмент оцінки: SQALE-матриця або простий варіант — оцінити кожен елемент боргу за двома осями: вартість не-лагодження за 6 місяців vs вартість лагодження. Перший квадрант (дорого не лагодити, дешево полагодити) — робимо негайно.
Приклад пріоритезації для iOS-застосунку:
| Елемент боргу |
Вартість ігнору |
Вартість лагодження |
Пріоритет |
UIWebView (видалено в iOS 15) |
App Store rejection |
2 дні |
Негайно |
Відсутність async/await, callbacks всюди |
+30% time-to-feature |
4 тижні |
Високий |
AsyncTask на Android (deprecated) |
Warning, не крэш |
1 тиждень |
Високий |
| Xcode storyboard vs SwiftUI |
Повільна розробка |
8+ тижнів |
Середній |
| Відсутність unit-тестів |
Регресії при змінах |
Поступово |
Високий |
Наш системний підхід до пріоритезації скорочує час усунення боргу в 2 рази порівняно з хаотичним вирішенням проблем. Методологія SQALE детально описана в офіційній документації.
Коли потрібна реструктуризація коду?
Якщо новий feature вимагає зміни існуючого коду більше, ніж написання нового — це дзвіночок. Наприклад, додавання Universal Links зачіпає 10 файлів, хоча має бути два. Ми використовуємо аналіз графа залежностей та метрики цикломатичної складності, щоб об'єктивно оцінити архітектуру. Якщо цикломатична складність модуля перевищує 15, це сигнал до рефакторингу.
Як виявляти та усувати продуктивнісний борг?
Це окрема історія. Memory leak на iOS — Instruments Leaks profiler, граф сильних посилань. Типовий винуватець: closure захоплює self без [weak self], self захоплює closure в didSet — retain cycle. У Swift Concurrency: Task із захопленням actor — теж вміє текти.
Android: StrictMode в debug-збірці негайно виявляє операції з диском на main thread (StrictMode.setThreadPolicy). LeakCanary — обов'язковий інструмент, ловить memory leaks автоматично і пише зрозумілий stack trace. LeakCanary виявляє витоки в 3 рази швидше ручного аналізу.
Кейс: Flutter-застосунок, більше двох років у продакшні. Скопився борг: http пакет 0.13 (застарів, dio всюди, але обидва підключені), provider 5.x та riverpod 1.x одночасно для різних фіч, немає null-safety міграції в 60% коду. Dart analysis видавав 340 warnings, CI фактично не працював — забагато хибних спрацьовувань. Працювали поетапно: спочатку null-safety migration (dart migrate --apply-changes), потім уніфікація state management на Riverpod 2.x, потім видалення дублюючих HTTP-пакетів. Три місяці, паралельно з feature-розробкою. Warnings: 340 → 12.
Процес усунення без зупинки розробки
«Заморозимо фічі на місяць і все полагодимо» — нереалістично і не потрібно. Працюємо за схемою:
- Audit & Roadmap (3–5 днів): аналіз кодової бази, класифікація боргу, створення дорожньої карти.
- Critical fixes (1–3 тижні): deprecated API, security issues, immediate blockers.
- Debt Sprint Budget (20% кожного спринту): рефакторинг, тести, документація.
- Continuous improvement (кожен PR): code review з перевіркою «залишаємо код кращим, ніж знайшли».
Таблиця етапів
| Етап |
Що робимо |
Результат |
| Audit & Roadmap |
Аналіз кодової бази, класифікація боргу |
Дорожня карта з пріоритетами |
| Critical fixes |
Виправлення deprecated API, security |
Стабільна збірка, без warnings |
| Debt Sprint Budget |
20% спринту на рефакторинг |
Зменшення боргу, зростання швидкості |
| Continuous improvement |
Code review, тести |
Запобігання новому боргу |
Отримайте консультацію інженера для узгодження плану робіт.
Що входить до складу робіт?
- Детальний звіт про технічний борг з пріоритезацією за SQALE.
- План поетапного виправлення з оцінкою трудозатрат.
- Впровадження практик Debt Sprint Budget та інспекцій коду.
- Документування змін та навчання команди.
- Гарантія: після кожного етапу — регресійні тести та демонстрація результату.
Чек-лист аудиту техборгу
- Перевірка актуальності залежностей (
flutter pub outdated, pod outdated).
- Аналіз всіх warnings у білді. Якщо >0, розбираємо кожен.
- Перевірка коду на memory leaks (Instruments, LeakCanary).
- Оцінка наявності та покриття тестів критичних модулів.
- Відповідність мінімальної версії платформи вимогам App Store / Google Play.
Наш досвід
Багаторічний досвід на ринку, понад 40 проєктів з оптимізації мобільних застосунків. Сертифіковані розробники iOS (Swift, SwiftUI) та Android (Kotlin, Jetpack Compose), досвід з Flutter та React Native. Кожен проєкт завершується документацією та планом подальшої підтримки. Середня економія наших клієнтів після рефакторингу суттєва і залежить від обсягу робіт. Отримайте консультацію інженера по вашому проєкту.
MVVM, Clean Architecture, BLoC, VIPER, TCA: проєктуємо архітектуру під ключ
Додаток зібрано в одному ViewController на 2000 рядків. Мережеві виклики, бізнес-логіка, оновлення UI — все в одному місці. Додати нову фічу без регресії складно, написати тест неможливо. Це не «поганий код» — це відсутність архітектури. І це трапляється частіше, ніж можна очікувати, навіть у production-додатках з мільйоном користувачів.
Ми проєктуємо архітектуру під ключ: від вибору паттерну до повної структури проєкту з тестами та документацією. За 7–10 днів отримуєте чистий модульний код, готовий до масштабування. Ваша команда зможе додавати нові фічі без ризику зламати існуючі, а тести покриють ключову логіку вже на старті. Архітектурні паттерни в мобайлі вирішують одне завдання: відокремити UI від логіки так, щоб кожна частина була тестованою та замінною.
MVVM — базовий паттерн
Model-View-ViewModel — стандарт для iOS (SwiftUI + Combine/async, UIKit + Combine) та Android (Jetpack ViewModel + StateFlow + Compose). ViewModel містить стан UI та бізнес-логіку. View лише відображає стан і передає наміри користувача до ViewModel. Model — дані та їх джерело.
Ключове правило: ViewModel не знає про UIKit або Android View-класи. Немає імпортів UIKit, немає Context-залежностей (крім Application context через Hilt). Це гарантує тестованість: ViewModel тестується як чистий Kotlin/Swift-код без Android Instrumented Test.
MVVM закриває 70% потреб. Інші 30% — де потрібна строга ізоляція фіч, масштабування команди, складний flow управління станом.
Clean Architecture — коли MVVM недостатньо
Додає шари поверх MVVM:
Domain-шар — бізнес-логіка, незалежна від платформи. UseCase (або Interactor) містить одне бізнес-правило: GetUserOrdersUseCase, PlaceOrderUseCase. Залежить лише від інтерфейсів (protocol/interface), не від конкретних реалізацій.
Data-шар — реалізація репозиторіїв. OrderRepositoryImpl реалізує OrderRepository з domain. Знає про Retrofit, Room, UserDefaults. ViewModel не знає, звідки дані — з мережі чи кешу.
Presentation-шар — ViewModel + View. Знає про Domain, не знає про Data.
Dependency rule: залежності спрямовані лише всередину. Domain не залежить ні від чого. Data та Presentation залежать від Domain.
Presentation → Domain ← Data
Це дає можливість підміняти реалізацію: тест використовує in-memory репозиторій замість мережевого, інтерфейс залишається тим самим.
Практичне зауваження: Clean Architecture додає файли та шари. Для невеликого додатку це overhead. Виправдано від ~15 фіч та при команді 3+ розробників.
BLoC для Flutter — передбачуваний потік станів
BLoC (Business Logic Component) — стандартний паттерн у Flutter-спільноті. Бібліотека flutter_bloc реалізує його через два типи: Bloc (Event → State) та Cubit (State без Events, лише методи).
Bloc обробляє Event та емітує новий State через on<EventType> хендлери. Стан імутабельний — новий об'єкт на кожну зміну. BlocBuilder перемальовує лише ту частину дерева, де змінився state.
// Подія
alias CartEvent {}
class AddItemToCart extends CartEvent {
final String productId;
AddItemToCart(this.productId);
}
// Стан
alias CartState {}
class CartLoaded extends CartState {
final List<CartItem> items;
CartLoaded(this.items);
}
// Bloc
class CartBloc extends Bloc<CartEvent, CartState> {
CartBloc(this._cartRepository) : super(CartLoaded([])) {
on<AddItemToCart>(_onAddItem);
}
Future<void> _onAddItem(AddItemToCart event, Emitter<CartState> emit) async {
final current = state as CartLoaded;
final updated = await _cartRepository.addItem(event.productId);
emit(CartLoaded(updated));
}
}
Перевага BLoC — тестованість. blocTest з bloc_test пакету дозволяє перевірити: при такому-то Event, з таким-то початковим State, BLoC має емітувати такий-то State. Без UI, без моків для Flutter-фреймворку.
VIPER — для великих iOS-проєктів
VIPER (View, Interactor, Presenter, Entity, Router) — найбільш строгий поділ обов'язків для iOS. Кожен компонент має протокол та конкретну реалізацію.
- View — лише UI, делегує все Presenter
- Interactor — бізнес-логіка, робота з мережею та даними
- Presenter — посередник між View та Interactor, форматує дані для View
- Entity — моделі даних (чисті структури)
- Router — навігація між модулями
Кожен модуль (екран або фіча) — окремий VIPER-модуль. Це виключає coupling між фічами та дозволяє великим командам працювати паралельно без конфліктів.
Ціна: багато файлів, багато протоколів. Шаблонний код генерується через Sourcery або кастомні Xcode-шаблони. VIPER виправданий для додатків з 10+ розробниками та 50+ екранами.
TCA (The Composable Architecture)
TCA від Point-Free — більш сучасна альтернатива VIPER для iOS/macOS. Основні концепції: State (імутабельний стан фічі), Action (всі можливі події), Reducer (State + Action → новий State + Effect), Store (зберігає State, обробляє Actions).
Scope дозволяє composable будувати великі фічі з маленьких: батьківський Reducer делегує частину State дочірньому. Кожна фіча тестується ізольовано через TestStore з точним контролем над Effects.
TCA має круту криву навчання, але дає передбачуваність, яку складно отримати іншим способом: кожна зміна стану — явний Action з конкретним джерелом.
Як обрати архітектуру мобільних додатків?
Оцінимо проєкт за 1 день — підберемо архітектуру з урахуванням розміру команди, платформи та планів зростання.
| Паттерн |
Платформа |
Команда |
Коли обирати |
| MVVM |
iOS, Android, Flutter |
1–5 |
Стартовий стандарт, MVP, невеликі проєкти |
| MVVM + Clean |
iOS, Android |
3–10 |
Середні проєкти, тестованість критична |
| BLoC |
Flutter |
2–8 |
Flutter з передбачуваним state management |
| VIPER |
iOS |
5–20 |
Великі iOS-проєкти, модульна архітектура |
| TCA |
iOS/macOS |
3–15 |
Сувора тестованість, Swift Concurrency |
Універсальної відповіді немає. Архітектуру обирають під розмір команди, вимоги до тестованості та горизонт підтримки додатку.
Що входить у нашу роботу
Процес складається з кількох кроків:
-
Аудит поточної архітектури (якщо додаток вже існує) — виявимо вузькі місця та регресійні зони.
-
Проєктування модульної структури з чіткими межами шарів та правилами залежностей.
-
Створення каркасу проєкту (Scaffold) з впровадженням DI, організації папок та налаштування лінтерів.
-
Написання юніт-тестів для шару домену та ViewModel — мінімум 80% покриття ключових use case.
-
Підготовка документації — архітектурні діаграми, README з правилами модифікації коду, інструкція для онбордингу нових розробників.
-
Передача робочого репозиторію з CI-пайплайном (GitHub Actions / Bitrise), налаштованим запуском тестів та статичним аналізом.
Все це входить у вартість проєктування. Додатково — підтримка на етапі впровадження: консультації команди, code review перших pull request.
Що відбувається без архітектури
Типовий сценарій через 18 місяців без архітектури: 40% часу розробки йде на дебаг регресій. Новий розробник розбирається в коді тиждень перед тим, як зробити перший PR. Тести не пишуться, «тому що складно мокувати». Додавання нової фічі вимагає розуміння половини кодової бази.
Вибір архітектури на старті — це інвестиція з поверненням через 3–6 місяців. За нашими даними, правильно спроєктована архітектура з MVVM + Clean дає в 3 рази менше регресій порівняно з монолітним ViewController. А витрати на її впровадження окупаються за 2–3 спринти. Середня економія на усуненні дефектів після впровадження — до 40% часу, що для команди з п'яти осіб може означати понад 10 000 доларів на рік.
Згідно з рекомендаціями Apple щодо проєктування додатків, розділення обов'язків — ключовий фактор стійкості коду.
Чому варто довірити архітектуру професіоналам?
Неправильний вибір паттерну на старті веде до переписування половини коду через рік. Ми бачили десятки проєктів, де спроба заощадити на архітектурі оберталася багатомісячним рефакторингом. У нас за плечима 10+ років комерційної розробки, досвід роботи з додатками від 1 до 50 розробників, понад 100 успішно реалізованих проєктів. Ми допомагаємо уникнути типових помилок:
- Overengineering для простого MVP (призначаємо MVVM, а не VIPER).
- Відсутність dependency injection — підключаємо Hilt/Koin/Dagger вже на старті.
- Ігнорування тестованості — закладаємо протоколи/інтерфейси з першого коміту.
Зв'яжіться з нами через Telegram або email — отримайте безкоштовну оцінку вашого проєкту та рекомендацію щодо оптимальної архітектури. Замовте проєктування вже сьогодні — і за тиждень стартуйте з чистим, масштабованим кодом.
Додаткова таблиця: порівняння витрат на впровадження
| Паттерн |
Час проєктування |
Кількість файлів на 1 екран |
Час написання тестів |
| MVVM |
2–3 дні |
5–7 |
1 день |
| MVVM + Clean |
4–5 днів |
10–12 |
2 дні |
| BLoC |
3–4 дні |
6–8 |
1.5 дня |
| VIPER |
5–7 днів |
12–15 |
2.5 дня |
| TCA |
5–6 днів |
8–10 |
2 дні |
Час вказано для команди з 2–3 розробників. З нашим шаблоном (generator) стартовий каркас готовий за 1 день незалежно від обраного паттерну.