Non-Fatal Error — to błąd, który nie powoduje zakończenia działania aplikacji i pozwala kontynuować wykonywanie programu. W przeciwieństwie do fatal error, błędy niekrytyczne mogą zostać przechwycone, obsłużone i zalogowane bez utraty sesji użytkownika. Według danych Firebase Crashlytics Documentation, 2024, około 70% wszystkich zarejestrowanych błędów w aplikacjach produkcyjnych to błędy niekrytyczne, ale ich ignorowanie prowadzi do narastania długu technicznego i stopniowego pogarszania doświadczenia użytkownika. Prawidłowa obsługa błędów non-fatal to jedna z kluczowych umiejętności programisty mobilnego.
Najważniejsze
Non-Fatal Error — to wyjątek lub stan błędny, który nie powoduje zakończenia procesu. Aplikacja nadal działa, ale może znajdować się w nieprawidłowym stanie: dane nie zostały załadowane, żądanie nie zostało wysłane, element interfejsu nie został wyświetlony. Użytkownik albo nie zauważa błędu, albo widzi komunikat i kontynuuje korzystanie z aplikacji.
Błąd niekrytyczny zawsze pozostawia programowi ścieżkę do przywrócenia. Procedura obsługi błędów może zaproponować alternatywne dane, ponowić operację lub pokazać zastępczy interfejs. Głównym zadaniem jest niedopuszczenie do crasha i utrzymanie doświadczenia użytkownika na akceptowalnym poziomie. Programista musi jawnie przewidzieć scenariusz przywrócenia w każdym bloku catch.
Według danych Instabug 2024, 65% użytkowników usuwa aplikację po dwóch nieudanych interakcjach. Błędy non-fatal pozostawione bez uwagi kumulują się i obniżają ogólną jakość działania. Systematyczne logowanie i naprawianie błędów niekrytycznych to bezpośrednia droga do zwiększenia retention i poprawy ocen użytkowników w sklepach z aplikacjami.
Błędy sieciowe — najczęstszy typ błędów non-fatal w aplikacjach mobilnych. Limit czasu połączenia, utrata sieci, nieprawidłowy kod statusu serwera — wszystkie te sytuacje są przechwytywane i obsługiwane bez crasha. Użytkownikowi wyświetlany jest komunikat o niedostępności usługi z propozycją ponowienia próby. Dla błędów sieciowych typowy jest wzorzec retry z wykładniczym opóźnieniem.
Nieprawidłowy format odpowiedzi serwera, brak obowiązkowego pola, nieprawidłowy typ danych — błędy parsowania są niekrytyczne, jeśli aplikacja prawidłowo obsługuje nieprawidłowe dane. Typowym podejściem jest użycie domyślnych wartości zastępczych i logowanie błędu parsowania z kontekstem żądania do późniejszej analizy na serwerze.
Problemy z ładowaniem obrazów, nieprawidłowe czcionki, błędy w layoutach — wszystkie nie są krytyczne, ale pogarszają wrażenia użytkownika. Obrazy zastępcze i wartości fallback pozwalają uniknąć pustych ekranów i czynią błędy mniej zauważalnymi. W React Native do błędów UI używa się Error Boundary z wyświetleniem komponentu zastępczego.
Błędy w obliczeniach, niezgodność stanów, nieprawidłowe przejścia między ekranami — błędy logiczne często nie prowadzą do crasha, ale powodują nieprawidłowe działanie aplikacji. Są trudniejsze do wykrycia bez systematycznego logowania i monitorowania, ponieważ nie generują raportów crasha i pozostają niezauważone aż do skargi użytkownika.
Non-Fatal Error różni się od fatal tym, że pozostawia programowi możliwość kontynuowania pracy. Fatal error — to stan, z którego aplikacja nie może się przywrócić: dereferencja null-owskaznika, przepełnienie stosu, brak pamięci. Błąd non-fatal można przechwycić, obsłużyć i kontynuować wykonanie, podczas gdy fatal error wymaga ponownego uruchomienia aplikacji.
| Cecha | Non-Fatal Error | Fatal Error |
|---|---|---|
| Zakończenie aplikacji | Nie | Tak |
| Możliwość przywrócenia | Tak, przez blok catch | Nie |
| Logowanie | Z kodu przez recordException | Tylko przez raporter crasha |
| Wpływ na UX | Tymczasowa niedogodność | Całkowita utrata sesji |
| Przykład | Network timeout, parse error | NullPointerException, OOM |
Granica między non-fatal a fatal może zależeć od implementacji. Limit czasu sieci w jednej aplikacji jest obsługiwany jako non-fatal (ponowienie żądania po 1–2 sekundach), w innej może być fatalny (crash przy braku procedury obsługi). Jakościowa obsługa błędów zamienia potencjalnie fatalne sytuacje w niekrytyczne, zwiększając stabilność aplikacji. Projektowanie systemu obsługi błędów to jedno z kluczowych zadań architektonicznych przy tworzeniu aplikacji mobilnej z wysokimi wymaganiami niezawodnościowymi. Wbudowany system monitorowania pozwala zespołowi szybko wykrywać i usuwać błędy niekrytyczne, zanim wpłyną one na znaczną liczbę użytkowników.
Firebase Crashlytics — główne narzędzie do logowania błędów niekrytycznych w aplikacjach mobilnych. Metoda recordException pozwala zarejestrować wyjątek non-fatal z pełnym stosem wywołań i kontekstem wykonania, nie przerywając działania aplikacji. W przeciwieństwie do raportów crasha, recordException można wywołać w dowolnym miejscu kodu do logowania przechwyconych wyjątków.
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: używamy danych zastępczych
Crashlytics.recordException(e)
showFallbackContent()
}
}
// Logowanie z kluczami użytkownika
Crashlytics.setCustomKey("screen", "Profile")
Crashlytics.setCustomKey("api_version", "v3")
Sentry — alternatywa dla Crashlytics z bardziej szczegółową diagnostyką błędów non-fatal. Sentry SDK udostępnia metodę captureException, która wysyła szczegóły wyjątku na serwer. Kluczową zaletą Sentry jest grupowanie podobnych błędów non-fatal w jedno issue, analiza częstotliwości występowania i kontekst wykonania w postaci breadcrumbs — sekwencji działań użytkownika przed błędem.
Nie wszystkie błędy non-fatal należy logować. Oczekiwane stany — brak sieci przy braku połączenia — można logować wybiórczo. Nieoczekiwane błędy — NullPointerException w obsłużonym kodzie, nieprawidłowy format danych, logic error — powinny być logowane zawsze. Każdy zespół określa próg istotności: średnio od 10 do 20 unikalnych błędów non-fatal na 1000 użytkowników dziennie uważa się za normę. Ważne jest skonfigurowanie alertów na gwałtowny wzrost liczby błędów non-fatal — może to wskazywać na problemy z nową wersją API lub regresję po wydaniu.
Podstawowym mechanizmem obsługi jest try-catch, który przechwytuje wyjątek i wykonuje kod przywracania. Dla operacji sieciowych typowym wzorcem jest ponowienie żądania z wykładniczym opóźnieniem (retry with backoff). Dla błędów parsowania — użycie domyślnych wartości zastępczych i logowanie kontekstu do późniejszej analizy po stronie serwera.
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)
}
Typy Result — alternatywne podejście bez wyjątków. Funkcja zwraca sealed class Result z wariantami Success i Failure. Kod wywołujący jawnie obsługuje oba warianty, co eliminuje nieobsłużone błędy. Typy Result są popularne w Kotlin (Result
Dla każdego typu błędu non-fatal należy przewidzieć strategię przywracania: ładowanie danych z cache przy błędzie sieciowym, użycie domyślnych wartości przy błędzie parsowania, ponowna inicjalizacja komponentu przy błędzie UI. Dobrą praktyką jest wyświetlenie użytkownikowi toasta lub snackbara z komunikatem o błędzie, ale nie blokowanie całkowicie interakcji z aplikacją. Ważne jest rozróżnianie błędów recoverable (do przywrócenia) i non-recoverable — dla tych drugich strategia przywracania będzie inna, na przykład propozycja ponownego uruchomienia ekranu lub wyczyszczenia danych. Buforowanie poprzedniego udanego stanu często okazuje się najprostszym i najbardziej efektywnym sposobem obsługi błędów non-fatal na platformach mobilnych.
Często zadawane pytania
Warning — to ostrzeżenie kompilatora lub analizatora statycznego o potencjalnym problemie w kodzie. Non-fatal error — to wyjątek runtime, który już wystąpił, ale nie doprowadził do crasha. Warning można usunąć przed kompilacją, non-fatal error — obsłużyć podczas wykonania przez blok catch.
Nie, nadmierne logowanie zaśmieca monitoring. Warto logować nieoczekiwane błędy w production i ignorować oczekiwane stany: brak sieci przy braku połączenia loguje się wybiórczo, a NullPointerException w obsłużonym kodzie — zawsze. Każdy zespół określa próg istotności w zależności od kontekstu aplikacji.
W SwiftUI używa się ObservableObject z polem @Published errorState do śledzenia stanu błędu. View subskrybuje zmiany i wyświetla alternatywną treść. Przed iOS 17 stosowano Combine z procedurami obsługi, od iOS 17 — SwiftData i makra @Observable do reaktywnej aktualizacji UI.
Tak, jeśli błąd wywołuje reakcję łańcuchową. Przykład: niekrytyczny błąd ładowania obrazu może prowadzić do nieprawidłowego stanu UI, który następnie powoduje crash przy próbie wyświetlenia. Jakościowa obsługa błędów non-fatal na każdym poziomie zapobiega ich eskalacji do poziomu fatalnego.
W iOS błędy non-fatal są obsługiwane przez do-catch z throw, w Android — przez try-catch z wyjątkami. iOS używa NSError z domenami i kodami błędów, Android — wyjątków Java/Kotlin. Crashlytics działa tak samo na obu platformach przez recordException, zapewniając jednolity interfejs do monitorowania.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również