الاستثناءات في Dart: try و catch و throw
المستخدم يكتب حروفاً في خانة الرقم، والإنترنت ينقطع فجأة، والملف المطلوب محذوف. التطبيق الجيد لا ينهار في هذه المواقف، بل يعرض رسالة مفهومة ويكمل عمله. رأيت try و catch سريعاً في درس البرمجة غير المتزامنة، وفي هذا الدرس ستتعلم الاستثناءات (Exceptions) بالتفصيل.
ما هو الاستثناء؟
Section titled “ما هو الاستثناء؟”الاستثناء حدث غير متوقع يقطع سير البرنامج الطبيعي. تخيّل جرس الإنذار في مبنى: عندما يرن، يتوقف الجميع عن عملهم العادي، وينتقلون إلى خطة الطوارئ. وإذا لم تكن هناك خطة، تعمّ الفوضى ويتوقف البرنامج كله.
خطة الطوارئ في Dart تُكتب هكذا:
void main() { try { var number = int.parse('abc'); print(number); } on FormatException catch (e) { print('النص ليس رقماً: ${e.message}'); } print('البرنامج مستمر');}النص ليس رقماً: Invalid radix-10 numberالبرنامج مستمر- داخل
tryنضع الكود الذي قد يفشل. int.parseلم تستطع تحويل'abc'، فأطلقت استثناءً من نوعFormatException، وتوقف تنفيذtryفوراً (لذلك لم يُنفَّذ سطر الطباعة الذي بعدها).on FormatExceptionتلتقط هذا النوع بالتحديد، وcatch (e)تعطينا كائن الخطأ لنقرأ تفاصيله.- بعد المعالجة أكمل البرنامج عمله بشكل طبيعي.
التقاط أكثر من نوع، و finally
Section titled “التقاط أكثر من نوع، و finally”يمكن كتابة عدة on، واحدة لكل نوع خطأ، ثم catch عامة لأي شيء آخر، ثم finally التي تُنفَّذ دائماً:
void check(String text, int index) { try { var parts = text.split(','); var value = int.parse(parts[index]); print('القيمة: $value'); } on FormatException { print('ليست رقماً'); } on RangeError { print('الفهرس خارج الحدود'); } catch (e) { print('خطأ غير متوقع: $e'); } finally { print('انتهى الفحص'); }}
void main() { check('10,20,30', 1); check('10,x,30', 1); check('10,20,30', 7);}القيمة: 20انتهى الفحصليست رقماًانتهى الفحصالفهرس خارج الحدودانتهى الفحصDart تجرّب البلوكات بالترتيب وتدخل أول بلوك يطابق نوع الخطأ. لذلك اكتب الأنواع المحددة أولاً، واترك catch العامة للآخر. أما finally فظهرت رسالتها في الحالات الثلاث، وهي المكان المناسب لإخفاء مؤشر التحميل أو إغلاق ملف.
| الصيغة | متى تستخدمها |
|---|---|
on النوع |
تعرف نوع الخطأ ولا تحتاج تفاصيله |
on النوع catch (e) |
تعرف النوع وتحتاج كائن الخطأ |
catch (e) |
أي خطأ آخر لم تتوقعه |
finally |
كود يُنفَّذ في كل الأحوال |
إطلاق استثناء: throw
Section titled “إطلاق استثناء: throw”أنت أيضاً تستطيع أن تدق جرس الإنذار عندما تصل لدالتك قيمة غير مقبولة. ولتطبيقك أن يعرّف استثناءاته الخاصة: كلاس عادي يكتب implements Exception:
class InsufficientBalanceException implements Exception { final double missing; InsufficientBalanceException(this.missing);
@override String toString() => 'الرصيد غير كافٍ، ينقصك $missing جنيه';}
class Wallet { double balance; Wallet(this.balance);
void pay(double amount) { if (amount <= 0) { throw ArgumentError('المبلغ يجب أن يكون أكبر من صفر'); } if (amount > balance) { throw InsufficientBalanceException(amount - balance); } balance -= amount; }}
void main() { var wallet = Wallet(100); try { wallet.pay(30); print('الرصيد الآن: ${wallet.balance}'); wallet.pay(250); print('هذا السطر لن يُنفَّذ'); } on InsufficientBalanceException catch (e) { print(e); }}الرصيد الآن: 70.0الرصيد غير كافٍ، ينقصك 180.0 جنيهالدفعة الأولى نجحت. والثانية أطلقت استثناءنا الخاص، فقفز التنفيذ من داخل pay إلى بلوك on مباشرة. والاستثناء الخاص يحمل معه بيانات مفيدة (المبلغ الناقص)، فتستطيع الواجهة عرض رسالة دقيقة للمستخدم.
تجنّب الاستثناء من الأساس
Section titled “تجنّب الاستثناء من الأساس”أحياناً توجد طريقة ألطف من انتظار الخطأ. الدالة int.tryParse تعيد null بدلاً من إطلاق استثناء، فتتعامل معها بأدوات Null Safety:
void main() { var good = int.tryParse('42'); var bad = int.tryParse('abc');
print(good); print(bad); print(bad ?? 0);}42null0جرّب بنفسك
Section titled “جرّب بنفسك”اكتب دالة double divide(int a, int b) تطلق ArgumentError برسالة عربية إذا كان b صفراً، وتعيد ناتج القسمة لغير ذلك. استدعها مرتين داخل try: مرة بـ 10 و 4، ومرة بـ 5 و 0، والتقط الخطأ واطبع رسالته من الخاصية message:
2.5خطأ: لا يمكن القسمة على صفرأخطاء شائعة
Section titled “أخطاء شائعة”catchفارغة تبتلع الخطأ:catch (e) {}تخفي المشكلة، فتبحث عنها ساعات. اطبع الخطأ أو اعرض رسالة على الأقل.- وضع
catchالعامة أولاً: ستلتقط كل شيء، ولن تصل Dart إلى بلوكاتonالتي بعدها. - كود كثير داخل
try: يصعّب معرفة السطر الذي فشل. ضع فيها العملية الحساسة فقط. - إطلاق نص:
throw 'خطأ'مسموح في Dart لكنه أسلوب سيئ، لأنه لا يمكن التقاطه بالنوع. أطلق كائن استثناء.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
متى يُنفَّذ بلوك finally؟
finally يُنفَّذ في كل الأحوال، ولذلك يناسب أعمال التنظيف.
سؤال
ماذا تعيد int.tryParse('abc')؟
tryParse لا تطلق استثناءً، بل تعيد null عند فشل التحويل. أما parse فهي التي تطلق الاستثناء.
الخلاصة
Section titled “الخلاصة”- ضع الكود الذي قد يفشل في
try، وعالج الخطأ فيonأوcatch. - رتّب البلوكات من النوع الأكثر تحديداً إلى الأعم.
finallyتُنفَّذ دائماً.throwتطلق استثناءً، ويمكنك تعريف استثناءات خاصة بـimplements Exception.- دوال مثل
tryParseتغنيك عن الاستثناء عندما يكون الفشل متوقعاً.