Non-Fatal Error — 애플리케이션을 종료하지 않고 프로그램 실행을 계속할 수 있는 오류입니다. 치명적 오류와 달리 비치명적 오류는 사용자 세션을 잃지 않고 캐치, 처리 및 로깅할 수 있습니다. Firebase Crashlytics 문서, 2024에 따르면 프로덕션 애플리케이션에서 로깅된 모든 오류의 약 70%가 비치명적이지만, 이를 무시하면 기술 부채가 축적되고 사용자 경험이 점진적으로 저하됩니다. 비치명적 오류의 올바른 처리는 모바일 개발자의 핵심 기술 중 하나입니다.
핵심 요점
Non-Fatal Error — 프로세스 종료를 유발하지 않는 예외 또는 오류 상태입니다. 애플리케이션은 계속 실행되지만 데이터가 로드되지 않음, 요청이 전송되지 않음, 인터페이스 요소가 표시되지 않음 등 잘못된 상태에 있을 수 있습니다. 사용자는 오류를 인지하지 못하거나 메시지를 보고 애플리케이션 사용을 계속합니다.
비치명적 오류는 항상 프로그램에 복구 경로를 남깁니다. 오류 처리기는 대체 데이터를 제공하거나, 작업을 재시도하거나, 인터페이스 플레이스홀더를 표시할 수 있습니다. 주요 목표는 크래시를 방지하고 허용 가능한 사용자 경험을 유지하는 것입니다. 개발자는 각 catch 블록에서 명시적으로 복구 시나리오를 계획해야 합니다.
Instabug 2024에 따르면, 65%의 사용자가 두 번의 실패한 상호작용 후 앱을 제거합니다. 방치된 비치명적 오류는 축적되어 전반적인 품질을 저하시킵니다. 비치명적 오류의 체계적인 로깅 및 수정은 사용자 유지율을 높이고 앱 스토어 평점을 개선하는 직접적인 경로입니다.
네트워크 오류는 모바일 애플리케이션에서 가장 흔한 비치명적 오류 유형입니다. 연결 시간 초과, 네트워크 손실, 잘못된 서버 상태 코드 — 이러한 모든 상황은 크래시 없이 캐치 및 처리됩니다. 사용자에게 재시도 옵션과 함께 서비스 이용 불가 메시지가 표시됩니다. 지수 백오프를 사용한 재시도 패턴은 네트워크 오류에서 일반적입니다.
잘못된 서버 응답 형식, 필수 필드 누락, 유효하지 않은 데이터 형식 — 파싱 오류는 애플리케이션이 잘못된 데이터를 올바르게 처리하면 비치명적입니다. 일반적인 접근 방식은 기본 폴백 값을 사용하고 나중에 서버 측 분석을 위해 요청 컨텍스트와 함께 파싱 오류를 로깅하는 것입니다.
이미지 로딩 문제, 잘못된 글꼴, 레이아웃 오류 — 모두 비치명적이지만 사용자 경험을 저하시킵니다. 플레이스홀더 이미지와 폴백 값은 빈 화면을 방지하고 오류를 덜 눈에 띄게 만듭니다. React Native에서는 폴백 컴포넌트를 표시하는 Error Boundary가 UI 오류에 사용됩니다.
계산 오류, 상태 불일치, 잘못된 화면 전환 — 논리 오류는 종종 크래시를 유발하지 않지만 애플리케이션의 잘못된 동작으로 이어집니다. 체계적인 로깅 및 모니터링 없이는 크래시 보고서를 생성하지 않고 사용자 불만까지 발견되지 않기 때문에 탐지가 어렵습니다.
Non-Fatal Error는 프로그램이 계속 작동할 기회를 남긴다는 점에서 치명적 오류와 다릅니다. 치명적 오류는 애플리케이션이 복구할 수 없는 상태입니다: null 포인터 역참조, 스택 오버플로, 메모리 부족. 비치명적 오류는 캐치, 처리 및 실행을 계속할 수 있지만, 치명적 오류는 애플리케이션을 다시 시작해야 합니다.
| 특성 | Non-Fatal Error | Fatal Error |
|---|---|---|
| 앱 종료 | 아니오 | 예 |
| 복구 가능 | 예, catch 블록을 통해 | 아니오 |
| 로깅 | 코드에서 recordException을 통해 | 크래시 리포터만 |
| UX 영향 | 일시적 불편 | 완전한 세션 실패 |
| 예시 | 네트워크 타임아웃, 파싱 오류 | NullPointerException, OOM |
비치명적과 치명적의 경계는 구현에 따라 달라질 수 있습니다. 한 애플리케이션에서 네트워크 타임아웃은 비치명적으로 처리되지만(1~2초 후 재시도), 다른 애플리케이션에서는 치명적일 수 있습니다(핸들러가 없으면 크래시). 품질 높은 오류 처리는 잠재적으로 치명적인 상황을 비치명적으로 전환하여 애플리케이션 안정성을 높입니다. 높은 신뢰성 요구 사항을 가진 모바일 애플리케이션을 개발할 때 오류 처리 시스템 설계는 주요 아키텍처 작업 중 하나입니다. 내장된 모니터링 시스템을 통해 팀은 비치명적 오류가 많은 사용자에게 영향을 미치기 전에 신속하게 감지하고 수정할 수 있습니다.
Firebase Crashlytics는 모바일 애플리케이션에서 비치명적 오류를 로깅하는 주요 도구입니다. recordException 메서드는 애플리케이션을 중단하지 않고 전체 스택 추적 및 실행 컨텍스트와 함께 비치명적 예외를 캡처할 수 있게 합니다. 크래시 보고서와 달리, 캐치된 예외를 로깅하기 위해 코드 어디에서든 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의 대안입니다. Sentry SDK는 captureException 메서드를 제공하여 서버에 예외 세부 정보를 전송합니다. Sentry의 주요 장점은 유사한 비치명적 오류를 단일 이슈로 그룹화하고, 재발 빈도를 분석하며, breadcrumbs(오류 발생 전 사용자 작업 순서) 형태로 실행 컨텍스트를 제공하는 것입니다.
모든 비치명적 오류를 로깅할 필요는 없습니다. 예상된 상태(연결이 없을 때의 네트워크 실패)는 선택적으로 로깅할 수 있습니다. 예상치 못한 오류(처리된 코드의 NullPointerException, 잘못된 데이터 형식, 논리 오류)는 항상 로깅해야 합니다. 각 팀은 중요도 임계값을 정의합니다: 평균적으로 하루에 사용자 1000명당 10~20개의 고유 비치명적 오류가 정상으로 간주됩니다. 비치명적 오류의 급증에 대한 알림을 설정하는 것이 중요합니다 — 이는 새 API 버전의 문제나 릴리스 후 회귀를 나타낼 수 있습니다.
기본 처리 메커니즘은 try-catch로, 예외를 캐치하고 복구 코드를 실행합니다. 네트워크 작업의 경우 지수 백오프를 사용한 재시도가 일반적인 패턴입니다. 파싱 오류의 경우 기본 폴백 값을 사용하고 나중에 서버 측 분석을 위해 컨텍스트를 로깅하는 접근 방식이 사용됩니다.
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 타입 — 예외 없는 대체 접근 방식입니다. 함수는 Success 및 Failure 변형이 있는 sealed 클래스 Result를 반환합니다. 호출 코드는 두 변형을 모두 명시적으로 처리하여 처리되지 않은 오류를 제거합니다. Result 타입은 Kotlin(표준 라이브러리의 Result
각 유형의 비치명적 오류에 대해 복구 전략을 계획해야 합니다: 네트워크 오류 시 캐시된 데이터 로드, 파싱 오류 시 기본값 사용, UI 오류 시 컴포넌트 재초기화. 좋은 방법은 애플리케이션과의 상호작용을 완전히 차단하지 않고 오류 메시지가 포함된 토스트 또는 스낵바를 사용자에게 표시하는 것입니다. 복구 가능한 오류와 복구 불가능한 오류를 구분하는 것이 중요합니다 — 후자의 경우 화면 재시작 제안이나 데이터 삭제 등 복구 전략이 다릅니다. 이전 성공 상태를 캐싱하는 것은 모바일 플랫폼에서 비치명적 오류를 처리하는 가장 간단하고 효과적인 방법인 경우가 많습니다.
자주 묻는 질문
경고는 코드의 잠재적 문제에 대한 컴파일러 또는 정적 분석기의 경고입니다. 비치명적 오류는 이미 발생했지만 크래시를 유발하지 않은 런타임 예외입니다. 경고는 컴파일 전에 수정할 수 있지만, 비치명적 오류는 catch 블록을 통해 실행 중에 처리해야 합니다.
아니요, 과도한 로깅은 모니터링을 복잡하게 만듭니다. 프로덕션에서는 예상치 못한 오류를 로깅하고 예상된 상태는 무시해야 합니다: 오프라인 시 네트워크 실패는 선택적으로 로깅할 수 있지만, 처리된 코드의 NullPointerException은 항상 로깅해야 합니다. 각 팀은 애플리케이션 컨텍스트에 따라 중요도 임계값을 정의합니다.
SwiftUI에서는 @Published errorState 필드가 있는 ObservableObject를 사용하여 오류 상태를 추적합니다. View는 변경 사항을 구독하고 대체 콘텐츠를 표시합니다. iOS 17 이전에는 핸들러와 함께 Combine이 사용되었고, iOS 17부터는 반응형 UI 업데이트를 위해 SwiftData와 @Observable 매크로가 사용됩니다.
네, 오류가 연쇄 반응을 일으키는 경우입니다. 예시: 비치명적 이미지 로딩 실패가 잘못된 UI 상태를 초래하고, 표시를 시도할 때 크래시를 유발할 수 있습니다. 각 수준에서 비치명적 오류를 품질 높게 처리하면 치명적 수준으로 확대되는 것을 방지할 수 있습니다.
iOS에서는 비치명적 오류가 throw와 함께 do-catch로 처리되고, Android에서는 예외와 함께 try-catch로 처리됩니다. iOS는 도메인과 오류 코드가 있는 NSError를 사용하고, Android는 Java/Kotlin 예외를 사용합니다. Crashlytics는 recordException을 통해 두 플랫폼에서 동일하게 작동하여 통합된 모니터링 인터페이스를 제공합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.