معالجة الأخطاء في Kotlin: try/catch وما بعدها - من الألف إلى الياء
معالجة الأخطاء هي إحدى المهام الأساسية لأي مطور. في Kotlin، كما في اللغات الحديثة الأخرى، توجد عدة طرق لإدارة الحالات الاستثنائية. في هذه المقالة، سنستعرض جميع الطرق الرئيسية: من try/catch التقليدي إلى التقنيات المتقدمة باستخدام Result وsealed class وNothing. ستتعلم كيفية كتابة كود موثوق وقابل للقراءة يعالج الأخطاء بشكل صحيح.
1. كتلة try/catch/finally التقليدية
الآلية الأساسية لمعالجة الاستثناءات في Kotlin هي كتلة try/catch. تعمل بنفس الطريقة كما في Java، ولكن مع بعض التحسينات. في Kotlin، try هي تعبير يمكن أن يُرجع قيمة.
fun parseNumber(input: String): Int? { return try { input.toInt() } catch (e: NumberFormatException) { println("خطأ في التحليل: ${e.message}") null // إرجاع null عند الخطأ } finally { println("كتلة finally تُنفذ دائمًا") }}
fun main() { val result = parseNumber("123abc") println(result) // null}الميزات الرئيسية:
- يمكن استخدام
tryكتعبير - نتيجة التعبير الأخير في كتلةtryأوcatchتصبح نتيجة الكتلة بأكملها. - كتلة
finallyتُنفذ في جميع الحالات (حتى في حالة وجود استثناء أوreturn). - لا توجد استثناءات مُتحقق منها (checked exceptions) في Kotlin كما في Java - مما يبسط الكود.
من المهم تذكر أن الإفراط في استخدام try/catch يجعل الكود أقل قابلية للقراءة. استخدمه فقط للحالات الاستثنائية حقًا، وليس للتحكم في تدفق البرنامج.
2. نوع Result والنهج الوظيفي
لمعالجة الأخطاء بشكل أكثر أناقة، تقدم Kotlin النوع Result<T>. وهو فئة مختومة يمكن أن تكون إما Success (مع النتيجة) أو Failure (مع الاستثناء). إنه مثالي للأسلوب الوظيفي.
import kotlin.Result
fun divide(a: Int, b: Int): Result<Int> { return try { Result.success(a / b) } catch (e: ArithmeticException) { Result.failure(e) }}
fun main() { val result = divide(10, 0) // المعالجة باستخدام when when (result) { is Result.Success -> println("النتيجة: ${result.value}") is Result.Failure -> println("خطأ: ${result.exception.message}") } // أو باستخدام الدوال result.onSuccess { value -> println("نجاح: $value") } .onFailure { e -> println("فشل: ${e.message}") } // التحويل val mapped = result.map { it * 2 } // إذا كان failure، سيبقى failure println(mapped)}مزايا Result:
- يُظهر بوضوح أن الدالة قد تنتهي بخطأ.
- يسمح بسلسلة من الاستدعاءات (
map،flatMap،recover). - لا يتطلب
try/catchفي الكود المستدعي.
ومع ذلك، فإن Result غير مخصص للتخزين في المجموعات أو النقل عبر حدود coroutines - لهذا من الأفضل استخدام sealed class.
3. الفئات المختومة لمعالجة الأخطاء التفصيلية
عند الحاجة إلى معالجة أنواع مختلفة من الأخطاء بطرق مختلفة، فإن sealed class هو الخيار الأمثل. يمكنك إنشاء تسلسل هرمي للأخطاء ومعالجتها في when بتغطية شاملة.
sealed class NetworkResult<out T> { data class Success<T>(val data: T) : NetworkResult<T>() data class NetworkError(val code: Int, val message: String) : NetworkResult<Nothing>() data class Timeout(val timeout