معالجة متقدمة للأخطاء في Kotlin: من try/catch إلى النهج الوظيفية
معالجة الأخطاء هي إحدى المهام الرئيسية عند تطوير برامج موثوقة. في Kotlin، بالإضافة إلى الآلية الكلاسيكية try/catch/finally، توجد مجموعة كاملة من الأدوات المتقدمة: Result، runCatching، sealed class والـ monads الوظيفية. في هذه المقالة، سنشرح كيفية تجاوز الاستخدام الأساسي وجعل كودك أكثر أمانًا وقابلية للقراءة وقابلية للتنبؤ.
1. try/catch الكلاسيكي: متى لا يزال مناسبًا
الـ try/catch الأساسي في Kotlin هو تعبير يمكنه إرجاع قيمة. هذا هو أول ما يميزه عن Java.
val result: String = try { riskyOperation()} catch (e: IOException) { "خطأ في الإدخال/الإخراج: ${e.message}"} catch (e: Exception) { "خطأ غير معروف: ${e.message}"} finally { println("كتلة finally تُنفَّذ دائمًا")}على الرغم من البساطة، إلا أن لهذا النهج عيوبًا:
- الاستثناءات عملية مكلفة (إنشاء stack trace).
- نوع الخطأ غير ثابت على مستوى الأنواع - من السهل تفويت المعالجة.
- يصبح الكود ضخمًا مع وجود عدد كبير من العمليات المتداخلة.
متى نستخدم try/catch؟
- عند العمل مع الموارد الخارجية (الملفات، الشبكة، قواعد البيانات).
- في كود المكتبات، حيث تكون الاستثناءات جزءًا من العقد.
- للأخطاء الحرجة التي لا يمكن معالجتها محليًا.
2. النهج الوظيفي: Result و runCatching
تقدم Kotlin فئة مدمجة Result، والتي تمثل نتيجة عملية قد تنتهي بالنجاح أو الخطأ. هذا بديل للاستثناءات لا يتطلب stack trace مكلفًا.
fun divide(a: Int, b: Int): Result<Int> { return runCatching { a / b }}
fun main() { val result = divide(10, 0) result.onSuccess { value -> println("النتيجة: $value") }.onFailure { error -> println("خطأ: ${error.message}") }}طرق Result
getOrNull()- تُرجع القيمة أو null.getOrDefault(default)- تُرجع القيمة أو القيمة الافتراضية.mapوflatMap- لسلاسل التحويلات.recover- الاسترداد بعد الخطأ.
val safeResult = runCatching { riskyOperation() } .map { it.toUpperCase() } .recover { "default_value" } .getOrDefault("fallback")هام: Result غير مصمم للنقل بين الخيوط أو التخزين في حقول الفئة - استخدمه فقط كقيمة محلية.
3. الفئات المختومة (Sealed classes): نمذجة الأخطاء على مستوى الأنواع
عندما تحتاج إلى تحديد جميع الحالات الممكنة بوضوح (النجاح، أنواع مختلفة من الأخطاء)، تأتي sealed class للإنقاذ. هذه أداة قوية لإنشاء أنواع البيانات الجبرية.
sealed class NetworkResult<out T> { data class Success<T>(val data: T) : NetworkResult<T>() data class Error(val message: String, val code: Int) : NetworkResult<Nothing>() object Loading : NetworkResult<Nothing>()}
fun fetchUser(id: Int): NetworkResult<User> { return try { val user = api.getUser(id) NetworkResult.Success(user) } catch (e: HttpException) { NetworkResult.Error("الخادم أرجع خطأ", e.code()) } catch (e: Exception) { NetworkResult.Error("خطأ غير معروف", 0) }}
fun handleResult(result: NetworkResult<User>) { when (result) { is NetworkResult.Success -> println("المستخدم: ${result.data.name}") is NetworkResult.Er