الاستثناءات (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?>