تخطَّ إلى المحتوى

الاستثناءات في Dart: try و catch و throw

الدرس 11 من 14 · Dart

المستخدم يكتب حروفاً في خانة الرقم، والإنترنت ينقطع فجأة، والملف المطلوب محذوف. التطبيق الجيد لا ينهار في هذه المواقف، بل يعرض رسالة مفهومة ويكمل عمله. رأيت try و catch سريعاً في درس البرمجة غير المتزامنة، وفي هذا الدرس ستتعلم الاستثناءات (Exceptions) بالتفصيل.

الاستثناء حدث غير متوقع يقطع سير البرنامج الطبيعي. تخيّل جرس الإنذار في مبنى: عندما يرن، يتوقف الجميع عن عملهم العادي، وينتقلون إلى خطة الطوارئ. وإذا لم تكن هناك خطة، تعمّ الفوضى ويتوقف البرنامج كله.

خطة الطوارئ في 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 كود يُنفَّذ في كل الأحوال

أنت أيضاً تستطيع أن تدق جرس الإنذار عندما تصل لدالتك قيمة غير مقبولة. ولتطبيقك أن يعرّف استثناءاته الخاصة: كلاس عادي يكتب 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);
}
الناتج
42
null
0

اكتب دالة double divide(int a, int b) تطلق ArgumentError برسالة عربية إذا كان b صفراً، وتعيد ناتج القسمة لغير ذلك. استدعها مرتين داخل try: مرة بـ 10 و 4، ومرة بـ 5 و 0، والتقط الخطأ واطبع رسالته من الخاصية message:

الناتج
2.5
خطأ: لا يمكن القسمة على صفر
  • catch فارغة تبتلع الخطأ: catch (e) {} تخفي المشكلة، فتبحث عنها ساعات. اطبع الخطأ أو اعرض رسالة على الأقل.
  • وضع catch العامة أولاً: ستلتقط كل شيء، ولن تصل Dart إلى بلوكات on التي بعدها.
  • كود كثير داخل try: يصعّب معرفة السطر الذي فشل. ضع فيها العملية الحساسة فقط.
  • إطلاق نص: throw 'خطأ' مسموح في Dart لكنه أسلوب سيئ، لأنه لا يمكن التقاطه بالنوع. أطلق كائن استثناء.

سؤال

متى يُنفَّذ بلوك finally؟

سؤال

ماذا تعيد int.tryParse('abc')؟

  • ضع الكود الذي قد يفشل في try، وعالج الخطأ في on أو catch.
  • رتّب البلوكات من النوع الأكثر تحديداً إلى الأعم.
  • finally تُنفَّذ دائماً.
  • throw تطلق استثناءً، ويمكنك تعريف استثناءات خاصة بـ implements Exception.
  • دوال مثل tryParse تغنيك عن الاستثناء عندما يكون الفشل متوقعاً.