الاستثناءات (Exceptions) في لغة Kotlin

من موسوعة حسوب

أصناف الاستثناءات (Exception Classes)

تنحدر كلّ أصناف الاستثناءات في لغة Kotlin من الصنف Throwable حيث يوجد لكلِّ استثناءٍ رسالةٌ للإعلام به وتتبُّع تكديسي (stack trace) وسببٌ اختياريّ.

ويُستخدَم التعبير throw لرمي كائن استثناءٍ بالشكل:

throw MyException("Hi There!")

أمّا تعبير try فهو مُستخدَمٌ لكشف الاستثناء كما في الشيفرة:

try {
    // شيفرة
}
catch (e: SomeException) {
    // معالجات الاستثناء
}
finally {
    // قسم اختياريّ
}

ومن الممكن أن يحتوي الاستثناء أكثر من قسمٍ واحدٍ من catch أو قد لا يُكتب بالأصل، كما ويمكن حذفُ القسم finally، ولكن من الضروريّ وجود قسمٍ واحدٍ على الأقل من catch أو finally في الاستثناء.

تعبير try

يُعدُّ قسم try تعبيرًا (expression) في Kotlin وبالتالي فمن الممكن أن يُعيد قيمةً كما في الشيفرة:

val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }

إذ تكون القيمة المُعادة من تعبير try إما بقيمة آخر تعبيرٍ واردٍ في القسم try أو بقيمة آخر تعبيرٍ في القسم catch (أو في الأقسام كلها إن كان هناك أكثر من قسمٍ واحدٍ من catch)، لأنّ وجود القسم finally لا يؤثِّر على النتيجة الإجماليّة للتعبير.

الاستثناءات المُتحقَّق منها (Checked Exceptions)

لا تدعم Kotlin هذا النوع من الاستثناءات لعدّة أسبابٍ نعرضها عبر مثالٍ بسيطٍ؛ تعبِّر الشيفرة الآتية عن واجهةٍ (interface) من JDK والتي يُعرَّف استخدامها (implemented) في الصنف StringBuilderبالشكل:

Appendable append(CharSequence csq) throws IOException;

وهذه الشيفرة تعني أنّه في كل مرةٍ تُلحَق فيها السلسلة بكائنٍ ما ( مثل StringBuillder أو log أو console أو ... إلخ.) تُعالَج الاستثناءات من النوع IOException ، ولكن لِمَ؟ لأنّه من الممكن أن تحدث العديد من عمليات الدخل أو الخرج (IO) (يُعرَّفُ استخدام Appendable في Writer أيضًا) وبالنتيجة سيكون هناك الكثير من هذه الشيفرة:

try {
    log.append(message)
}
catch (IOException e) {
    // يجب أن يكون آمنًا
}

وليس من الجيّد القيام بمثل هذا كما لا يجب تجاهل الاستثناءات، وخاصّةً في المشاريع الكبيرة فهذا سيخفِّض الإنتاجيّة دون تحسينٍ يُذكر في النوعيّة.

نوع اللاشيء (The Nothing Type)

يُعدُّ قسم throw كذلك تعبيرًا في لغة Kotlin ولهذا قد يُستخدَم كجزءٍ من تعبير Elvis (راجع أمان القيم الفارغة Null Safety) كما في الشيفرة:

val s = person.name ?: throw IllegalArgumentException("Name required")

إذ إنّ نوع التعبير throw هو من نوعٍ خاصٍ في Kotlin وهو Nothing والذي لا يحتوي على أيّة قيمةٍ، ويُستخدَم لتحديد الأجزاء التي لا يمكن الوصول إليها في الشيفرة، وبالإمكان استخدام هذا النوع كذلك مع الدوال (functions) التي لا عودة (return) فيها، كما في الشيفرة:

fun fail(message: String): Nothing {
    throw IllegalArgumentException(message)
}

فعند استدعاء الدالة سيعلم المترجم (لوجود النوع Nothing) بأنّ التنفيذ لن يستمرّ بما بعدها، مثل:

val s = person.name ?: fail("Name required")
println(s)     // من المعلوم أنّ المتغير مُهيَّأ هنا

و يفيد هذا النوع في حالةٍ أخرى عند تخمين الأنواع (type inference) حيث سيكون للنوع Nothing?‎ (وهو nullable) قيمةً ممكنةً واحدةً فقط وهي null وبالتالي فإنّه عند استخدام القيمة null للتهيئة الأوليّة لقيمة نوعٍ مُخمَّنٍ دون وجود أيّ معلومةٍ أخرى تُفيد في تحديد النوع، فسيجعل حينها المُترجِم (compiler) النوعَ من Nothing?‎ كما في الشيفرة:

val x = null           // المتغيِّر من النوع `Nothing?`
val l = listOf(null)   // المتغيِّر من النوع `List<Nothing?>

مصادر