Non-Fatal Error — là lỗi không làm kết thúc ứng dụng và cho phép tiếp tục thực thi chương trình. Không giống như lỗi nghiêm trọng, lỗi không nghiêm trọng có thể được bắt, xử lý và ghi log mà không mất phiên người dùng. Theo Tài liệu Firebase Crashlytics, 2024, khoảng 70% tất cả lỗi được ghi lại trong ứng dụng sản xuất là không nghiêm trọng, nhưng bỏ qua chúng dẫn đến tích tụ nợ kỹ thuật và suy giảm dần trải nghiệm người dùng. Xử lý đúng cách các lỗi không nghiêm trọng là một trong những kỹ năng quan trọng của nhà phát triển di động.
Những điểm chính
Non-Fatal Error — là một ngoại lệ hoặc trạng thái lỗi không gây ra việc kết thúc tiến trình. Ứng dụng tiếp tục hoạt động, nhưng có thể ở trạng thái không chính xác: dữ liệu không tải được, yêu cầu không được gửi, phần tử giao diện không hiển thị. Người dùng hoặc không nhận thấy lỗi, hoặc thấy thông báo và tiếp tục sử dụng ứng dụng.
Lỗi không nghiêm trọng luôn để lại cho chương trình một con đường khôi phục. Trình xử lý lỗi có thể cung cấp dữ liệu thay thế, thử lại thao tác hoặc hiển thị phần giữ chỗ giao diện. Mục tiêu chính là ngăn chặn sự cố và duy trì trải nghiệm người dùng chấp nhận được. Nhà phát triển phải lên kịch bản khôi phục rõ ràng trong mỗi khối catch.
Theo Instabug 2024, 65% người dùng gỡ cài đặt ứng dụng sau hai tương tác thất bại. Các lỗi không nghiêm trọng bị bỏ qua sẽ tích tụ và làm giảm chất lượng tổng thể. Việc ghi log và sửa lỗi không nghiêm trọng một cách có hệ thống là con đường trực tiếp để cải thiện tỷ lệ giữ chân người dùng và điểm đánh giá trên cửa hàng ứng dụng.
Lỗi mạng là loại lỗi không nghiêm trọng phổ biến nhất trong ứng dụng di động. Hết thời gian chờ kết nối, mất mạng, mã trạng thái máy chủ không chính xác — tất cả các tình huống này đều được bắt và xử lý mà không gây sự cố. Người dùng được hiển thị thông báo dịch vụ không khả dụng với tùy chọn thử lại. Mẫu thử lại với backoff theo cấp số nhân là điển hình cho lỗi mạng.
Định dạng phản hồi máy chủ không chính xác, thiếu trường bắt buộc, kiểu dữ liệu không hợp lệ — lỗi phân tích cú pháp là không nghiêm trọng nếu ứng dụng xử lý dữ liệu sai một cách chính xác. Cách tiếp cận điển hình là sử dụng giá trị dự phòng mặc định và ghi log lỗi phân tích với ngữ cảnh yêu cầu để phân tích sau ở phía máy chủ.
Sự cố tải hình ảnh, phông chữ không chính xác, lỗi bố cục — tất cả đều không nghiêm trọng nhưng làm giảm trải nghiệm người dùng. Hình ảnh giữ chỗ và giá trị dự phòng giúp tránh màn hình trống và làm cho lỗi ít bị chú ý hơn. Trong React Native, Error Boundary được sử dụng cho lỗi giao diện với việc hiển thị thành phần dự phòng.
Lỗi tính toán, không khớp trạng thái, chuyển đổi màn hình không chính xác — lỗi logic thường không gây ra sự cố nhưng dẫn đến hành vi không chính xác của ứng dụng. Chúng khó phát hiện hơn nếu không có ghi log và giám sát có hệ thống vì không tạo báo cáo sự cố và không bị phát hiện cho đến khi có khiếu nại của người dùng.
Non-Fatal Error khác với lỗi nghiêm trọng ở chỗ nó để lại cho chương trình cơ hội tiếp tục hoạt động. Lỗi nghiêm trọng là trạng thái mà ứng dụng không thể khôi phục: giải tham chiếu con trỏ null, tràn ngăn xếp, hết bộ nhớ. Lỗi không nghiêm trọng có thể được bắt, xử lý và tiếp tục thực thi, trong khi lỗi nghiêm trọng yêu cầu khởi động lại ứng dụng.
| Đặc điểm | Non-Fatal Error | Fatal Error |
|---|---|---|
| Kết thúc ứng dụng | Không | Có |
| Khả năng khôi phục | Có, qua khối catch | Không |
| Ghi log | Từ mã qua recordException | Chỉ bởi trình báo cáo sự cố |
| Tác động UX | Bất tiện tạm thời | Thất bại hoàn toàn phiên |
| Ví dụ | Hết thời gian mạng, lỗi phân tích | NullPointerException, OOM |
Ranh giới giữa không nghiêm trọng và nghiêm trọng có thể phụ thuộc vào cách triển khai. Hết thời gian mạng trong một ứng dụng được xử lý như không nghiêm trọng (thử lại sau 1–2 giây), trong khi ở ứng dụng khác, nó có thể là nghiêm trọng (sự cố nếu không có trình xử lý). Xử lý lỗi chất lượng cao biến các tình huống tiềm ẩn nghiêm trọng thành không nghiêm trọng, tăng độ ổn định của ứng dụng. Thiết kế hệ thống xử lý lỗi là một trong những nhiệm vụ kiến trúc chính khi phát triển ứng dụng di động với yêu cầu độ tin cậy cao. Một hệ thống giám sát tích hợp cho phép nhóm phát hiện và sửa lỗi không nghiêm trọng nhanh chóng trước khi chúng ảnh hưởng đến một số lượng lớn người dùng.
Firebase Crashlytics là công cụ chính để ghi log lỗi không nghiêm trọng trong ứng dụng di động. Phương thức recordException cho phép ghi lại một ngoại lệ không nghiêm trọng với dấu vết ngăn xếp đầy đủ và ngữ cảnh thực thi mà không làm gián đoạn ứng dụng. Không giống như báo cáo sự cố, recordException có thể được gọi ở bất kỳ đâu trong mã để ghi log các ngoại lệ đã bắt.
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: sử dụng dữ liệu dự phòng
Crashlytics.recordException(e)
showFallbackContent()
}
}
// Ghi log với khóa tùy chỉnh
Crashlytics.setCustomKey("screen", "Profile")
Crashlytics.setCustomKey("api_version", "v3")
Sentry là một giải pháp thay thế cho Crashlytics với chẩn đoán chi tiết hơn cho lỗi không nghiêm trọng. SDK Sentry cung cấp phương thức captureException, gửi chi tiết ngoại lệ đến máy chủ. Ưu điểm chính của Sentry là nhóm các lỗi không nghiêm trọng tương tự thành một vấn đề duy nhất, phân tích tần suất lặp lại và cung cấp ngữ cảnh thực thi dưới dạng breadcrumbs — chuỗi hành động của người dùng trước khi xảy ra lỗi.
Không phải tất cả lỗi không nghiêm trọng đều cần được ghi log. Trạng thái dự kiến — lỗi mạng khi không có kết nối — có thể được ghi log có chọn lọc. Lỗi không mong đợi — NullPointerException trong mã đã xử lý, định dạng dữ liệu không hợp lệ, lỗi logic — phải luôn được ghi log. Mỗi nhóm xác định ngưỡng quan trọng của riêng mình: trung bình, 10 đến 20 lỗi không nghiêm trọng duy nhất trên 1000 người dùng mỗi ngày được coi là bình thường. Việc thiết lập cảnh báo cho sự gia tăng đột biến lỗi không nghiêm trọng là rất quan trọng — điều này có thể chỉ ra vấn đề với phiên bản API mới hoặc hồi quy sau khi phát hành.
Cơ chế xử lý cơ bản là try-catch, bắt ngoại lệ và thực thi mã khôi phục. Đối với thao tác mạng, mẫu điển hình là thử lại với backoff theo cấp số nhân. Đối với lỗi phân tích, cách tiếp cận là sử dụng giá trị dự phòng mặc định và ghi log ngữ cảnh để phân tích sau ở phía máy chủ.
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)
}
Kiểu Result — một cách tiếp cận thay thế không có ngoại lệ. Một hàm trả về lớp sealed Result với các biến thể Success và Failure. Mã gọi xử lý cả hai biến thể một cách rõ ràng, loại bỏ các lỗi không được xử lý. Kiểu Result phổ biến trong Kotlin (Result
Đối với mỗi loại lỗi không nghiêm trọng, cần lên kế hoạch chiến lược khôi phục: tải dữ liệu được lưu trong bộ nhớ đệm khi lỗi mạng, sử dụng giá trị mặc định khi lỗi phân tích, khởi tạo lại thành phần khi lỗi giao diện. Một thực hành tốt là hiển thị cho người dùng toast hoặc snackbar với thông báo lỗi mà không chặn hoàn toàn tương tác với ứng dụng. Điều quan trọng là phân biệt giữa lỗi có thể khôi phục và không thể khôi phục — đối với loại sau, chiến lược khôi phục sẽ khác, như đề xuất khởi động lại màn hình hoặc xóa dữ liệu. Lưu trạng thái thành công trước đó vào bộ nhớ đệm thường là cách đơn giản và hiệu quả nhất để xử lý lỗi không nghiêm trọng trên các nền tảng di động.
Câu hỏi thường gặp
Cảnh báo là lời cảnh báo của trình biên dịch hoặc trình phân tích tĩnh về một vấn đề tiềm ẩn trong mã. Lỗi không nghiêm trọng là ngoại lệ thời gian chạy đã xảy ra nhưng không gây ra sự cố. Cảnh báo có thể được sửa trước khi biên dịch; lỗi không nghiêm trọng phải được xử lý trong quá trình thực thi thông qua khối catch.
Không, ghi log quá mức làm lộn xộn việc giám sát. Lỗi không mong đợi trong sản xuất nên được ghi log, trong khi trạng thái dự kiến nên được bỏ qua: lỗi mạng khi ngoại tuyến có thể được ghi log có chọn lọc, nhưng NullPointerException trong mã đã xử lý phải luôn được ghi log. Mỗi nhóm xác định ngưỡng quan trọng dựa trên ngữ cảnh của ứng dụng.
Trong SwiftUI, ObservableObject với trường @Published errorState được sử dụng để theo dõi trạng thái lỗi. View đăng ký thay đổi và hiển thị nội dung thay thế. Trước iOS 17, Combine với trình xử lý được sử dụng; từ iOS 17 trở đi, SwiftData và macro @Observable được sử dụng để cập nhật giao diện phản ứng.
Có, nếu lỗi gây ra phản ứng dây chuyền. Ví dụ: lỗi tải hình ảnh không nghiêm trọng có thể dẫn đến trạng thái giao diện không chính xác, sau đó gây ra sự cố khi cố gắng hiển thị. Xử lý lỗi không nghiêm trọng chất lượng cao ở mỗi cấp độ ngăn chặn chúng leo thang lên mức nghiêm trọng.
Trên iOS, lỗi không nghiêm trọng được xử lý qua do-catch với throw; trên Android, qua try-catch với ngoại lệ. iOS sử dụng NSError với miền và mã lỗi; Android sử dụng ngoại lệ Java/Kotlin. Crashlytics hoạt động giống hệt nhau trên cả hai nền tảng qua recordException, cung cấp giao diện giám sát thống nhất.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm