Kotlin / أسس اللغة والحالة الصريحة / الدرس 6
الفصل بين التحقق والاستثناءات والتنظيف
استخدم require وcheck وتعبيرات try والالتقاط الضيق وfinally، مع إبقاء إخفاقات الإدخال المتوقعة صريحة.
في هذه الصفحة
أهداف الدرس
- اختر فرض شرط مسبق أو نتيجة إدخال غير صالح وفقا للعقد مع المستدعي.
- اشرح نطاق catch والتنظيف دون ابتلاع إخفاقات غير مرتبطة بالحد المعالَج.
الاستثناء ليس الوسيلة الوحيدة لتمثيل الفشل
تستحق الحالات الخارجية المتوقعة والافتراضات الداخلية المكسورة معالجتين مختلفتين. فإدخال المستخدم عددا مشوها غالبا نتيجة تحقق عادية، بينما قد يدل استدعاء دالة بمعامل ممنوع على خطأ برمجي. تفحص require شرطا مسبقا متعلقا بالمعامل، وترمي IllegalArgumentException عند إخفاقه؛ وتفحص check حالة غير قانونية، وترمي IllegalStateException. هذه فحوص وقت تشغيل، وليست إثباتات من المترجم. حدّد مدخلات الدالة المقبولة، وقرّر هل يتفرع المستدعي وفقا لنتيجة أم يعد سوء الاستخدام استثنائيا، قبل اختيار الصياغة.
ارفض سوء الاستخدام قبل إنتاج نتيجة مضللة
يحسب المثال مجموع حجز بوحدات نقدية صغرى افتراضية. ويشترط عددا موجبا للمقاعد ومبلغا غير سالب للوحدة. تجعل الفحوص عقد الدالة ظاهرا، لكن ضرب Int العادي قد يظل يتجاوز مجاله مع مدخلات كبيرة بما يكفي. يحتاج حساب المال الحقيقي مجالات مبررة أو تمثيلا دقيقا مناسبا. الالتقاط في main ضيق عمدا ويطبع نتيجة آمنة ثابتة؛ فلا يكشف أثرا عشوائيا للاستدعاءات، ولا يزعم وجوب تحويل كل استثناء في التطبيق إلى النص نفسه.
fun subtotal(seats: Int, unitCents: Int): Int {
require(seats > 0) { "seats must be positive" }
require(unitCents >= 0) { "unit amount must be nonnegative" }
return seats * unitCents
}
fun main() {
println(subtotal(2, 150))
try {
subtotal(0, 150)
} catch (error: IllegalArgumentException) {
println("invalid argument")
}
}
300
invalid argument
المخرج المتوقع هو 300 ثم invalid argument. تقع مدخلات العرض ضمن مجال Int. لم يُترجم البرنامج، ولا ينفّذ دفعة حقيقية أو عملية محاسبية.
يمكن لتعبير try أن ينتج قيمة
تستطيع كتل try وcatch إنتاج نتيجة تُستخدم داخل تعبير أوسع. اجعل الكتلة المحمية ضيقة بحيث يكون للاستثناء الملتقط معنى غير ملتبس. إذا اجتمع التحليل والعمل على قاعدة بيانات تحت catch واسعة، فقد يوصف عيب تخزين خطأ بأنه مدخل غير صالح. فضّل toIntOrNull للتحليل المعتاد عندما لا تحتاج تفاصيل الاستثناء. وقد يخفي catch من نوع Exception حول كل دالة عيوبا برمجية، وقد يعيق إلغاء الكوروتينات ما لم تُحفظ إشارة الإلغاء. تتناول الدروس اللاحقة حد التزامن المنظم هذا صراحة.
تعالج finally التنظيف دون تغيير النتيجة
تعمل finally عندما يغادر التحكم بنية try المرتبطة بها وفقا لدلالات معالجة الاستثناءات العادية. استخدمها للتنظيف اللازم، وتجنب return داخلها لأنه قد يحجب عودة سابقة أو استثناء سابقا. على JVM، يستخدم كود الموارد كثيرا use مع أنواع موارد قابلة للإغلاق مناسبة، ليربط إغلاق المورد بالنطاق؛ ويهم نوع المورد وواجهة المنصة الفعلية. وقد يفشل التنظيف نفسه، لذلك ينبغي حفظ الإخفاق الأصلي المهم في التشخيص. لا يتضمن المثال ملفا أو موردا شبكيا، ويشرح الترتيب بالطباعة فقط.
fun parseOrFallback(text: String): Int = try {
text.toInt()
} catch (error: NumberFormatException) {
-1
} finally {
println("parse finished")
}
fun main() {
println(parseOrFallback("12"))
println(parseOrFallback("bad"))
}
parse finished
12
parse finished
-1
الأسطر المتوقعة هي parse finished ثم 12 ثم parse finished ثم -1. تطبع finally قبل أن يطبع المستدعي القيمة المعادة. تمثل -1 علامة تعليمية للفشل، ولا تناسب مجالا تكون فيه -1 قيمة بيانات صالحة.
ترجم الإخفاق في الطبقة التي تفهمه
يمكن لحد التحليل تفسير المدخل المشوه، ولحد التطبيق تقرير الإعادة أو الرفض، ولحد العرض اختيار صياغة آمنة. لا تفقد السبب برمي استثناء جديد غير مرتبط بلا سياق، ولا تعد نجاحا يبدو معقولا بصمت. ينبغي أن تحفظ السجلات أقل دليل تشخيصي مفيد دون أسرار أو حمولات شخصية كاملة. افحص في الاختبارات النتيجة المقصودة والحالة بعد الفشل، لا مجرد حدوث الالتقاط. تستخدم أمثلة الدورة قيما محلية ثابتة ومخرجات متوقعة بالتحليل؛ ولم يُختبر هنا تنظيف ملفات أو فشل مزود خارجي أو تنفيذ استثناءات JVM.
الأخطاء الشائعة وإصلاحها
يتحول كل إخفاق إلى رسالة مدخل غير صالح.
- لماذا يحدث؟
- يغطي catch عمليات غير مرتبطة وأنواع استثناءات لا يفهمها الحد نفسه.
- كيف تصلحه؟
- ضيّق الكتلة المحمية، وترجم الإخفاقات التي تفهم معناها فقط.
- كيف تتجنب تكراره؟
- أبق التحليل وسياسة المجال والإدخال والإخراج الخارجي حدودا منفصلة.
تحجب عودة داخل finally الاستثناء الأصلي.
- لماذا يحدث؟
- غيّر التنظيف تدفق التحكم بدلا من الحفاظ عليه.
- كيف تصلحه؟
- أزل العودة واجعل آثار التنظيف محدودة وواضحة.
- كيف تتجنب تكراره؟
- راجع مسارات التنظيف في حالتي النجاح والفشل.
تُفسَّر قيمة سالبة صالحة على أنها فشل تحليل.
- لماذا يحدث؟
- تداخلت العلامة العددية المستخدمة للفشل مع مجال البيانات المسموح.
- كيف تصلحه؟
- أعد null أو نتيجة منظمة بدلا من عدد ملتبس.
- كيف تتجنب تكراره؟
- تحقق من استحالة كون تمثيل الفشل قيمة بيانات صالحة في المجال.
اختبر فهمك
1. هل ينبغي لنموذج ويب استخدام require لكل حرف يكتبه المستخدم خطأ؟
اعرض الحل المشروح
عادة ينبغي للتطبيق تمثيل المدخل غير الصالح المتوقع كنتيجة تحقق وتقديم ملاحظات مفيدة للمستخدم. تناسب require الحالة التي يعني فيها خرق عقد معاملات الدالة سوء استخدام من المستدعي. يمكن لمحول الإدخال التحليل والتحقق أولا، ثم استدعاء دالة داخلية أشد قيودا. اجعل هذا الفصل صريحا بدلا من اختيار الاستثناءات لمجرد اختصار الشيفرة.
2. لماذا لا يكفي تنفيذ catch بنجاح لإثبات صحة الاستعادة؟
اعرض الحل المشروح
قد يترك البرنامج حالة جزئية، أو يفقد السبب الأصلي، أو يسرّب موردا، أو يعيد نجاحا مضللا. تحقّق من المخرج المقصود والقيد وحالة الموارد تحت الإخفاق المحدد. وبالنسبة إلى فعل خارجي، حدّد هل اكتملت العملية قبل إعادتها؛ فلا تستطيع catch محلية التراجع عن أثر بعيد ثُبّت بالفعل.
أهم ما تعلمته
- تحتاج إخفاقات الإدخال المتوقعة وسوء الاستخدام والحالة غير الصالحة إلى عقود مختلفة.
- يجب أن يحافظ التنظيف وترجمة الأخطاء على نتائج صادقة تعكس ما حدث.