Оптимизация технического долга мобильного приложения
Технический долг в мобильном приложении — это не абстракция. Это конкретная причина, по которой Xcode 15 падает с linker command failed при добавлении нового SPM-пакета, потому что три года назад кто-то добавил устаревший CocoaPod, который конфликтует с современным Swift Concurrency runtime.
Три типа долга с разной ценой
Долг инструментальный. Deprecated API, устаревшие SDK, поддержка iOS 13 когда App Store Connect уже требует iOS 16 minimum. Xcode выдаёт 200 warnings при каждой сборке — команда научилась игнорировать их все, включая те, которые предупреждают о реальных проблемах.
Долг архитектурный. Отсутствие разделения слоёв, прямые зависимости между фичами, тесты написать невозможно без поднятия всего приложения целиком. Стоимость каждой новой фичи растёт нелинейно.
Долг производительностный. Memory leaks в долгоживущих объектах, main thread stall при открытии экранов, Excessive CPU Usage в background tasks что вызывает thermal throttling на iPhone 12. Пользователь замечает, ставит 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-тестов | Регрессии при изменениях | Постепенно | Высокий |
Работа с производительностным долгом
Это отдельная история. 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.
Кейс: Flutter-приложение, 2.5 года в продакшне. Скопился долг: 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.
Процесс без остановки разработки
«Заморозим фичи на месяц и всё починим» — нереалистично и не нужно. Работаем по схеме:
- 20% каждого спринта выделяем на техдолг (Debt Sprint Budget)
- Критический долг (deprecated API, security issues) — отдельный хотфикс-трек
- Каждый новый PR не увеличивает долг: code review включает проверку «оставляем код лучше чем нашли»
Сроки: аудит и план работ — 3–5 дней. Устранение критического долга (deprecated API, security fixes) — 1–3 недели. Полная реструктуризация архитектурного долга — 2–4 месяца параллельно с разработкой.







