Non-Fatal Error — คือข้อผิดพลาดที่ไม่ทำให้แอปพลิเคชันหยุดทำงานและอนุญาตให้ดำเนินการโปรแกรมต่อไปได้ แตกต่างจาก fatal error ตรงที่ข้อผิดพลาดที่ไม่ร้ายแรงสามารถถูกจับ จัดการ และบันทึกได้โดยไม่สูญเสียเซสชันของผู้ใช้ ตาม เอกสาร Firebase Crashlytics, 2024 ประมาณ 70% ของข้อผิดพลาดทั้งหมดที่บันทึกในแอปพลิเคชันโปรดักชันเป็นแบบไม่ร้ายแรง แต่การละเลยข้อผิดพลาดเหล่านี้นำไปสู่การสะสมของหนี้ทางเทคนิคและการลดลงของคุณภาพประสบการณ์ผู้ใช้อย่างค่อยเป็นค่อยไป การจัดการข้อผิดพลาดที่ไม่ร้ายแรงอย่างถูกต้องเป็นหนึ่งในทักษะสำคัญของนักพัฒนามือถือ
ประเด็นสำคัญ
Non-Fatal Error — คือข้อยกเว้นหรือสถานะข้อผิดพลาดที่ไม่ทำให้กระบวนการสิ้นสุดลง แอปพลิเคชันยังคงทำงานต่อไป แต่อาจอยู่ในสถานะที่ไม่ถูกต้อง: ข้อมูลไม่โหลด คำขอไม่ถูกส่ง องค์ประกอบอินเทอร์เฟซไม่แสดง ผู้ใช้ไม่สังเกตเห็นข้อผิดพลาด หรือเห็นข้อความและใช้งานแอปพลิเคชันต่อไป
ข้อผิดพลาดที่ไม่ร้ายแรงมักจะปล่อยให้โปรแกรมมีเส้นทางสำหรับการกู้คืน ตัวจัดการข้อผิดพลาด สามารถให้ข้อมูลสำรอง ดำเนินการซ้ำ หรือแสดงตัวยึดตำแหน่งอินเทอร์เฟซ เป้าหมายหลักคือการป้องกันการขัดข้องและรักษาประสบการณ์ผู้ใช้ที่ยอมรับได้ นักพัฒนาต้องวางแผนสถานการณ์กู้คืนอย่างชัดเจนในแต่ละบล็อก catch
ตาม Instabug 2024 ผู้ใช้ 65% ถอนการติดตั้งแอปหลังจากสองการโต้ตอบที่ล้มเหลว ข้อผิดพลาดที่ไม่ร้ายแรงที่ถูกปล่อยไว้โดยไม่ดูแลจะสะสมและลดคุณภาพโดยรวม การบันทึกและแก้ไขข้อผิดพลาดที่ไม่ร้ายแรงอย่างเป็นระบบเป็นหนทางโดยตรงในการปรับปรุงการรักษาผู้ใช้และคะแนนในร้านค้าแอป
ข้อผิดพลาดเครือข่าย เป็นประเภทข้อผิดพลาดที่ไม่ร้ายแรงที่พบบ่อยที่สุดในแอปพลิเคชันมือถือ การหมดเวลาการเชื่อมต่อ การสูญเสียเครือข่าย รหัสสถานะเซิร์ฟเวอร์ไม่ถูกต้อง — สถานการณ์เหล่านี้ทั้งหมดถูกจับและจัดการโดยไม่ขัดข้อง ผู้ใช้จะเห็นข้อความบริการไม่พร้อมใช้งานพร้อมตัวเลือกให้ลองใหม่ รูปแบบการลองใหม่แบบ backoff แบบเอกซ์โพเนนเชียลเป็นเรื่องปกติสำหรับข้อผิดพลาดเครือข่าย
รูปแบบการตอบสนองของเซิร์ฟเวอร์ไม่ถูกต้อง ฟิลด์บังคับหายไป ชนิดข้อมูลไม่ถูกต้อง — ข้อผิดพลาดการแยกวิเคราะห์ จะไม่ร้ายแรงหากแอปพลิเคชันจัดการข้อมูลที่ไม่ถูกต้องอย่างเหมาะสม วิธีการทั่วไปคือใช้ค่าสำรองเริ่มต้นและบันทึกข้อผิดพลาดการแยกวิเคราะห์พร้อมบริบทของคำขอสำหรับการวิเคราะห์ฝั่งเซิร์ฟเวอร์ในภายหลัง
ปัญหาโหลดรูปภาพ แบบอักษรไม่ถูกต้อง ข้อผิดพลาดเลย์เอาต์ — ทั้งหมดไม่ร้ายแรงแต่ลดประสบการณ์ผู้ใช้ รูปภาพตัวยึดตำแหน่ง และค่าสำรองช่วยหลีกเลี่ยงหน้าจอว่างเปล่าและทำให้ข้อผิดพลาดสังเกตเห็นได้น้อยลง ใน React Native ใช้ Error Boundary สำหรับข้อผิดพลาด UI โดยแสดงคอมโพเนนต์สำรอง
ข้อผิดพลาดในการคำนวณ สถานะไม่ตรงกัน การเปลี่ยนหน้าจอไม่ถูกต้อง — ข้อผิดพลาดเชิงตรรกะ มักไม่ก่อให้เกิดการขัดข้องแต่นำไปสู่พฤติกรรมที่ไม่ถูกต้องของแอปพลิเคชัน ตรวจพบได้ยากหากไม่มีการบันทึกและการตรวจสอบอย่างเป็นระบบเนื่องจากไม่สร้างรายงานการขัดข้องและไม่มีใครสังเกตเห็นจนกว่าผู้ใช้จะร้องเรียน
Non-Fatal Error แตกต่างจาก fatal ตรงที่มันเปิดโอกาสให้โปรแกรมทำงานต่อไปได้ Fatal error เป็นสถานะที่แอปพลิเคชันไม่สามารถกู้คืนได้: การอ้างอิงพอยน์เตอร์เป็นโมฆะ สแต็กโอเวอร์โฟลว์ หน่วยความจำไม่เพียงพอ ข้อผิดพลาดที่ไม่ร้ายแรงสามารถถูกจับ จัดการ และดำเนินการต่อไปได้ ในขณะที่ fatal error ต้องรีสตาร์ทแอปพลิเคชัน
| คุณลักษณะ | 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 คือการจัดกลุ่มข้อผิดพลาดที่ไม่ร้ายแรงที่คล้ายกันเป็น issue เดียว วิเคราะห์ความถี่ในการเกิดซ้ำ และให้บริบทการทำงานเป็น breadcrumbs — ลำดับการกระทำของผู้ใช้ก่อนเกิดข้อผิดพลาด
ไม่จำเป็นต้องบันทึกข้อผิดพลาดที่ไม่ร้ายแรงทั้งหมด สถานะที่คาดหวัง — เครือข่ายล้มเหลวเมื่อไม่มีการเชื่อมต่อ — สามารถบันทึกแบบเลือกได้ ข้อผิดพลาดที่ไม่คาดคิด — NullPointerException ในโค้ดที่จัดการ รูปแบบข้อมูลไม่ถูกต้อง ข้อผิดพลาดเชิงตรรกะ — ควรบันทึกเสมอ แต่ละทีมกำหนดเกณฑ์ความสำคัญของตนเอง: โดยเฉลี่ย 10 ถึง 20 ข้อผิดพลาดที่ไม่ร้ายแรงที่ไม่ซ้ำกันต่อผู้ใช้ 1000 คนต่อวันถือว่าปกติ การตั้งค่าการแจ้งเตือนสำหรับการเพิ่มขึ้นอย่างรวดเร็วของข้อผิดพลาดที่ไม่ร้ายแรงเป็นสิ่งสำคัญ — นี้อาจบ่งบอกถึงปัญหากับเวอร์ชัน API ใหม่หรือการถดถอยหลังจากการเผยแพร่
กลไกการจัดการพื้นฐานคือ try-catch ซึ่งจับข้อยกเว้นและดำเนินการโค้ดกู้คืน สำหรับการดำเนินการเครือข่าย รูปแบบทั่วไปคือการลองใหม่แบบ 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 Result พร้อมตัวแปร Success และ Failure โค้ดที่เรียกจัดการทั้งสองตัวแปรอย่างชัดเจน ซึ่งกำจัดข้อผิดพลาดที่ไม่ได้รับการจัดการ ประเภท Result เป็นที่นิยมใน Kotlin (Result
สำหรับข้อผิดพลาดที่ไม่ร้ายแรงแต่ละประเภท ควรวางแผน กลยุทธ์กู้คืน: โหลดข้อมูลที่แคชไว้เมื่อเกิดข้อผิดพลาดเครือข่าย ใช้ค่าเริ่มต้นเมื่อเกิดข้อผิดพลาดการแยกวิเคราะห์ รีเริ่มต้นคอมโพเนนต์เมื่อเกิดข้อผิดพลาด UI แนวปฏิบัติที่ดีคือแสดง toast หรือ snackbar พร้อมข้อความข้อผิดพลาดให้ผู้ใช้เห็นโดยไม่บล็อกการโต้ตอบกับแอปพลิเคชันทั้งหมด สิ่งสำคัญคือต้องแยกความแตกต่างระหว่างข้อผิดพลาดที่กู้คืนได้และกู้คืนไม่ได้ — สำหรับข้อผิดพลาดประเภทหลัง กลยุทธ์กู้คืนจะแตกต่างกัน เช่น แนะนำให้รีสตาร์ทหน้าจอหรือล้างข้อมูล การแคชสถานะที่สำเร็จก่อนหน้านี้มักเป็นวิธีที่ง่ายและมีประสิทธิภาพที่สุดในการจัดการข้อผิดพลาดที่ไม่ร้ายแรงบนแพลตฟอร์มมือถือ
คำถามที่พบบ่อย
คำเตือน คือการเตือนจากคอมไพเลอร์หรือตัววิเคราะห์สแตติกเกี่ยวกับปัญหาที่อาจเกิดขึ้นในโค้ด ข้อผิดพลาดที่ไม่ร้ายแรงคือข้อยกเว้นรันไทม์ที่เกิดขึ้นแล้วแต่ไม่ทำให้เกิดการขัดข้อง คำเตือนสามารถแก้ไขได้ก่อนการคอมไพล์ ข้อผิดพลาดที่ไม่ร้ายแรงต้องจัดการระหว่างการทำงานผ่านบล็อก catch
ไม่ การบันทึกมากเกินไปทำให้การตรวจสอบรกรุงรัง ข้อผิดพลาดที่ไม่คาดคิด ในโปรดักชันควรบันทึก ในขณะที่สถานะที่คาดหวังควรละเว้น: เครือข่ายล้มเหลวเมื่อออฟไลน์สามารถบันทึกแบบเลือกได้ แต่ NullPointerException ในโค้ดที่จัดการควรบันทึกเสมอ แต่ละทีมกำหนดเกณฑ์ความสำคัญตามบริบทของแอปพลิเคชัน
ใน SwiftUI ใช้ ObservableObject พร้อมฟิลด์ @Published errorState เพื่อติดตามสถานะข้อผิดพลาด View สมัครสมาชิกการเปลี่ยนแปลงและแสดงเนื้อหาสำรอง ก่อน iOS 17 ใช้ Combine พร้อมตัวจัดการ ตั้งแต่ iOS 17 เป็นต้นไป ใช้ SwiftData และแมโคร @Observable สำหรับการอัปเดต UI แบบรีแอกทีฟ
ได้ หากข้อผิดพลาดก่อให้เกิดปฏิกิริยาลูกโซ่ ตัวอย่าง: การโหลดรูปภาพล้มเหลวที่ไม่ร้ายแรงสามารถนำไปสู่สถานะ UI ที่ไม่ถูกต้อง ซึ่งจากนั้นทำให้เกิดการขัดข้องเมื่อพยายามแสดง การจัดการข้อผิดพลาดที่ไม่ร้ายแรงอย่างมีคุณภาพในแต่ละระดับป้องกันการยกระดับไปสู่ระดับร้ายแรง
ใน iOS ข้อผิดพลาดที่ไม่ร้ายแรงถูกจัดการผ่าน do-catch พร้อม throw ใน Android ผ่าน try-catch พร้อมข้อยกเว้น iOS ใช้ NSError พร้อมโดเมนและรหัสข้อผิดพลาด Android ใช้ข้อยกเว้น Java/Kotlin Crashlytics ทำงานเหมือนกันบนทั้งสองแพลตฟอร์มผ่าน recordException ให้อินเทอร์เฟซการตรวจสอบแบบรวม
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม