Проведення код-ревью мобільного додатку
Код-ревью, яке зводиться до «переименуй змінну» та «додай коментар» — не ревью. Реальне ревью мобільного коду шукає місця, де додаток впаде у продакшене: memory leak у замиканні, race condition у async коді, неправильний lifecycle обробник, який ловить події після deinit.
Що перевіряємо в першу чергу
Memory management на iOS. Retain cycles через [weak self] — це знають всі, але часто роблять неправильно. Типовий баг: таймер тримає сильну ссилку на ViewController через target: self, ViewController тримає таймер — цикл. deinit ніколи не викличеться, екран не звільниться, пам'ять росте. Перевіряємо всі Timer.scheduledTimer, NotificationCenter.addObserver, DispatchQueue.asyncAfter — везде, де self захоплений без weak/unowned.
Друга частина — @escaping замикання в мережевих запитах: якщо запит скасований, але колбек все ж приходить і звертається до deallocated ViewController — краш. Перевіряємо [weak self] + guard let self = self else { return } у кожному escaping completion.
Concurrency і data races на iOS (Swift Concurrency). Після переходу на async/await та Actors з'явилися нові паттерни помилок: звернення до @MainActor-ізольованої властивості з non-isolated контексту без await, захоплення Sendable-порушуючих типів у Task. Xcode Thread Sanitizer знаходить частину проблем, але не всі — потрібен manual review з розумінням Actor isolation rules.
Android: lifecycle та ViewModel. LiveData.observe(this, ...) всередині Fragment — this як LifecycleOwner. Якщо не використовувати viewLifecycleOwner повсюдно, спостерігач залишається живим після знищення View, оновлення даних застосовуються до detached View — краш NullPointerException або дублювання спостерігачів при повернення на фрагмент. Перевіряємо кожен observe у Fragment.
Корутини та скасування. viewModelScope.launch — правильно, корутина скасовується при очищенні ViewModel. GlobalScope.launch — червоний прапор у ревью: живе довше ViewModel, не скасовується, тримає ссилки. lifecycleScope.launch у Fragment — перевіряємо, що не запускаємо з onCreate, а з onViewCreated, інакше кілька підписок при кожному пересоздання view.
Архітектурні паттерни
Дивимося на зв'язність компонентів: ViewModel напряму звертається до Context? Use case знає про шар презентації? Repository імпортує android.view.*? Це порушення Clean Architecture, які роблять код нетестуємим та крихким.
Для Flutter: перевіряємо, немає ліквідної логіки в StatefulWidget.build — вона повинна бути в Bloc/Cubit/ViewModel. Прямі вклики setState з API-запитами всередині — ознака архітектурного боргу.
Безпека та типові вразливості
- Токени у
UserDefaults/SharedPreferencesplaintext - Логування чутливих даних через
print/Log.d— у release-збірці логи видні черезadb logcat - SQL-запити через конкатенацію строк замість prepared statements (Room не дозволяє це зробити випадково, але прямі SQLiteDatabase-вклики — можуть)
- Deeplink handling без валідації параметрів — open redirect або injection через кастомну схему
Формат ревью
По кожній знайденій проблемі — конкретний файл, строка, пояснення чому це проблема та приклад исправління. Ніяких «слід розглянути рефакторинг» — або це баг/ризик з пріоритетом, або незначна рекомендація.
Пріоритизація: Critical (краш, вразливість, витік даних), High (memory leak, неверний lifecycle), Medium (архітектурний борг, нетестуємість), Low (стиль, найменування).
Терміни — 2–3 дні на проект середнього розміру (50–100 файлів). Великі кодові бази (200+ файлів) — до 5 днів.







