Non-Fatal Error — это ошибка, которая не приводит к завершению работы приложения и позволяет продолжить выполнение программы. В отличие от fatal error, нефатальные ошибки могут быть перехвачены, обработаны и залогированы без потери пользовательского сеанса. По данным Firebase Crashlytics Documentation, 2024, около 70% всех зарегистрированных ошибок в production-приложениях являются нефатальными, но их игнорирование ведёт к накоплению технического долга и постепенному ухудшению пользовательского опыта. Корректная обработка non-fatal ошибок — один из ключевых навыков мобильного разработчика.
Главное
Non-Fatal Error — это исключение или ошибочное состояние, которое не вызывает завершение процесса. Приложение продолжает работать, но может находиться в некорректном состоянии: не загрузились данные, не отправился запрос, не отобразился элемент интерфейса. Пользователь либо не замечает ошибку, либо видит сообщение и продолжает использование приложения.
Нефатальная ошибка всегда оставляет программе путь для восстановления. Обработчик ошибок может предложить альтернативные данные, повторить операцию или показать заглушку интерфейса. Основная задача — не допустить crash и сохранить пользовательский опыт на приемлемом уровне. Разработчик должен явно предусмотреть сценарий восстановления в каждом catch-блоке.
По данным Instabug 2024, 65% пользователей удаляют приложение после двух неудачных взаимодействий. Non-fatal ошибки, оставленные без внимания, накапливаются и снижают общее качество работы. Систематическое логирование и исправление нефатальных ошибок — прямой путь к повышению retention и улучшению пользовательских оценок в магазинах приложений.
Сетевые ошибки — самый распространённый тип non-fatal ошибок в мобильных приложениях. Таймаут соединения, потеря сети, неверный статус-код сервера — все эти ситуации перехватываются и обрабатываются без crash. Пользователю показывается сообщение о недоступности сервиса с предложением повторить попытку. Для сетевых ошибок типичен паттерн retry с экспоненциальной задержкой.
Неверный формат ответа от сервера, отсутствие обязательного поля, некорректный тип данных — ошибки парсинга являются нефатальными, если приложение корректно обрабатывает некорректные данные. Типичный подход — использование запасных значений по умолчанию и логирование ошибки парсинга с контекстом запроса для последующего анализа на сервере.
Проблемы с загрузкой изображений, некорректные шрифты, ошибки в layout — все они не фатальны, но ухудшают впечатление пользователя. Placeholder-изображения и fallback-значения позволяют избежать пустых экранов и делают ошибки менее заметными. В React Native для UI-ошибок используют Error Boundary с отображением запасного компонента.
Ошибки в расчётах, несоответствие состояний, неправильные переходы между экранами — логические ошибки часто не приводят к crash, но приводят к некорректному поведению приложения. Их сложнее обнаружить без систематического логирования и мониторинга, так как они не создают crash-репорт и остаются незамеченными до жалобы пользователя.
Non-Fatal Error отличается от fatal тем, что оставляет программе возможность продолжать работу. Fatal error — это состояние, из которого приложение не может восстановиться: разыменование null-указателя, переполнение стека, нехватка памяти. Non-fatal ошибку можно перехватить, обработать и продолжить выполнение, в то время как fatal error требует перезапуска приложения.
| Характеристика | Non-Fatal Error | Fatal Error |
|---|---|---|
| Завершение приложения | Нет | Да |
| Возможность восстановления | Да, через catch-блок | Нет |
| Логирование | Из кода через recordException | Только crash-репортером |
| UX-влияние | Временное неудобство | Полный сбой сеанса |
| Пример | Network timeout, parse error | NullPointerException, OOM |
Граница между non-fatal и fatal может зависеть от реализации. Сетевой таймаут в одном приложении обрабатывается как non-fatal (повтор запроса через 1–2 секунды), в другом может быть фатальным (crash при отсутствии обработчика). Качественная обработка ошибок превращает потенциально фатальные ситуации в нефатальные, повышая стабильность приложения. Проектирование системы обработки ошибок — одна из ключевых архитектурных задач при разработке мобильного приложения с высокими требованиями к надёжности. Встроенная система мониторинга позволяет команде быстро обнаруживать и устранять нефатальные ошибки до того, как они повлияют на значительное число пользователей.
Firebase Crashlytics — основной инструмент для логирования нефатальных ошибок в мобильных приложениях. Метод recordException позволяет зафиксировать non-fatal исключение с полным стектрейсом и контекстом выполнения, не прерывая работу приложения. В отличие от crash-репортов, recordException можно вызывать в любом месте кода для логирования перехваченных исключений.
fun fetchUserData(userId: String) {
try {
val response = apiService.getUser(userId)
updateUI(response)
} catch (e: IOException) {
Crashlytics.log("Network error for user $userId")
Crashlytics.recordException(e)
showRetryDialog()
} catch (e: JsonParseException) {
// Non-fatal: используем запасные данные
Crashlytics.recordException(e)
showFallbackContent()
}
}
// Логирование с пользовательскими ключами
Crashlytics.setCustomKey("screen", "Profile")
Crashlytics.setCustomKey("api_version", "v3")
Sentry — альтернатива Crashlytics с более детальной диагностикой non-fatal ошибок. Sentry SDK предоставляет метод captureException, который отправляет детали исключения на сервер. Ключевое преимущество Sentry — группировка похожих non-fatal ошибок в одно issue, анализ частоты повторения и контекст выполнения в виде breadcrumbs — последовательности действий пользователя перед ошибкой.
Не все non-fatal ошибки нужно логировать. Ожидаемые состояния — отказ сети при отсутствии соединения — можно логировать выборочно. Неожиданные ошибки — NullPointerException в обработанном коде, неверный формат данных, logic error — должны логироваться всегда. Каждая команда определяет порог значимости: в среднем от 10 до 20 уникальных non-fatal ошибок на 1000 пользователей в день считается нормой. Важно настроить алерты на резкий рост числа non-fatal ошибок — это может указывать на проблемы с новой версией API или регрессию после релиза.
Базовый механизм обработки — try-catch, который перехватывает исключение и выполняет код восстановления. Для сетевых операций типичный паттерн — повтор запроса с экспоненциальной задержкой (retry with backoff). Для ошибок парсинга — использование запасных значений по умолчанию и логирование контекста для последующего анализа на серверной стороне.
func loadImage(from url: URL) -> UIImage? {
do {
let data = try Data(contentsOf: url)
return UIImage(data: data)
} catch {
Logger.shared.logError(error: "Image load failed: \(url)")
return UIImage(named: "placeholder")
}
}
func performRequest() async throws -> Data {
var lastError: Error? = nil
for attempt in 0..<3 {
do {
return try await URLSession.shared.data(from: url)
} catch {
lastError = error
try await Task.sleep(UInt64(pow(2, attempt)) * 1_000_000_000)
}
}
throw lastError ?? URLError(.unknown)
}
Result-типы — альтернативный подход без исключений. Функция возвращает sealed class Result с вариантами Success и Failure. Вызывающий код обрабатывает оба варианта явно, что исключает необработанные ошибки. Result-типы популярны в Kotlin (Result
Для каждого типа non-fatal ошибки нужно предусмотреть стратегию восстановления: загрузка кэшированных данных при сетевой ошибке, использование значений по умолчанию при ошибке парсинга, повторная инициализация компонента при UI-ошибке. Хорошая практика — показывать пользователю toast или snackbar с сообщением об ошибке, но не блокировать взаимодействие с приложением полностью. Важно различать recoverable (восстановимые) и non-recoverable ошибки — для вторых стратегия восстановления будет другой, например, предложение перезапустить экран или очистить данные. Кэширование предыдущего успешного состояния часто оказывается простейшим и наиболее эффективным способом обработки non-fatal ошибок на мобильных платформах.
Часто задаваемые вопросы
Warning — это предупреждение компилятора или статического анализатора о потенциальной проблеме в коде. Non-fatal error — это runtime-исключение, которое уже произошло, но не привело к crash. Warning можно устранить до компиляции, non-fatal error — обработать во время выполнения через catch-блок.
Нет, избыточное логирование зашумляет мониторинг. Стоит логировать неожиданные ошибки в production и игнорировать ожидаемые состояния: отказ сети при отсутствии соединения логируется выборочно, а NullPointerException в обработанном коде — всегда. Каждая команда определяет порог значимости исходя из контекста приложения.
В SwiftUI используется ObservableObject с @Published полем errorState для отслеживания ошибочного состояния. View подписывается на изменения и отображает альтернативный контент. До iOS 17 применялся Combine с обработчиками, начиная с iOS 17 — SwiftData и @Observable макросы для реактивного обновления UI.
Да, если ошибка вызывает цепную реакцию. Пример: нефатальный сбой загрузки изображения может привести к некорректному состоянию UI, которое затем вызывает crash при попытке отображения. Качественная обработка non-fatal ошибок на каждом уровне предотвращает их эскалацию до фатального уровня.
В iOS non-fatal ошибки обрабатываются через do-catch с throw, в Android — через try-catch с исключениями. iOS использует NSError с доменами и кодами ошибок, Android — Java/Kotlin исключения. Crashlytics работает одинаково на обеих платформах через recordException, предоставляя единый интерфейс для мониторинга.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также