معالجة الأخطاء في Kotlin: try/catch وما بعدها
معالجة الأخطاء هي جزء أساسي من كتابة كود موثوق ومتين. في Kotlin، كما في Java، توجد آلية try/catch/finally الكلاسيكية، لكن اللغة تقدم أيضًا طرقًا أكثر حداثة ووظيفية. في هذه المقالة، سنقوم بتحليل جميع طرق معالجة الاستثناءات في Kotlin بالتفصيل: من try/catch الأساسي إلى runCatching الأنيق و Result و sealed class القوية.
1. الطريقة الكلاسيكية try/catch/finally
بناء جملة معالجة الاستثناءات الأساسي في Kotlin مطابق تقريبًا لـ Java، ولكن مع اختلاف مهم واحد: يمكن أن يكون try تعبيرًا، مما يعني أنه يمكنه إرجاع قيمة.
مثال 1: try كتعبير
fun parseIntOrNull(input: String): Int? { return try { input.toInt() } catch (e: NumberFormatException) { null }}
fun main() { val result = parseIntOrNull("123") println(result) // 123
val badResult = parseIntOrNull("abc") println(badResult) // null}مثال 2: كتل catch متعددة
fun processInput(input: String) { try { val number = input.toInt() val result = 100 / number println("النتيجة: $result") } catch (e: NumberFormatException) { println("خطأ: تنسيق رقم غير صحيح") } catch (e: ArithmeticException) { println("خطأ: قسمة على صفر") } finally { println("كتلة finally تُنفذ دائمًا") }}كتلة finally تُنفذ في جميع الأحوال - سواء عند التنفيذ الناجح أو عند حدوث استثناء. هذا مفيد لتحرير الموارد (إغلاق الملفات، الاتصالات).
2. عامل إلفيس (?:) والاستدعاءات الآمنة
يقدم Kotlin طريقة أنيقة لمعالجة قيم null، والتي غالبًا ما تساعد في تجنب الإنشاءات الضخمة لـ try/catch.
مثال: تحويل آمن باستخدام عامل إلفيس
fun safeStringToInt(input: String): Int { return input.toIntOrNull() ?: 0 // إذا كانت null، نرجع 0}
fun main() { println(safeStringToInt("42")) // 42 println(safeStringToInt("abc")) // 0}الدالة toIntOrNull() هي بديل مدمج في Kotlin لـ try/catch. إنها لا ترمي استثناءً، بل ترجع null عند حدوث خطأ. عامل إلفيس ?: يضع قيمة افتراضية.
3. النهج الوظيفي: runCatching و Result
يوصي Kotlin الحديث باستخدام الأسلوب الوظيفي لمعالجة الأخطاء. الأدوات الرئيسية هي runCatching والكلاس Result.
مثال: runCatching
import kotlin.Result
fun divide(a: Int, b: Int): Result<Int> { return runCatching { a / b }}
fun main() { val successResult = divide(10, 2) val failureResult = divide(10, 0)
// معالجة النتيجة successResult .onSuccess { println("نجاح: $it") } // نجاح: 5 .onFailure { println("خطأ: ${it.message}") }
failureResult .onSuccess { println("نجاح: $it") } .onFailure { println("خطأ: ${it.message}") } // خطأ: / by zero
// الحصول على القيمة مع قيمة افتراضية val value1 = successResult.getOrDefault(-1) // 5 val value2 = failureResult.getOrDefault(-1) // -1
// تحويل النتيجة val mappedResult = successResult.map { it * 2 } // Result(10)}مزايا runCatching:
- يصبح الكود تصريحيًا وقابلاً للقراءة.
- لا حاجة لكتابة
try/catchيدويًا. - يمكن دمج النتيجة وتحويلها ومعالجتها بسهولة عبر سلاسل الاستدعاءات (
.map).