Dart: معالجة الاستثناءات في Dart

آخر تحديث: 2026-08-26

معالجة الاستثناءات هي شبكة الأمان لكودك — بدونها، خطأ واحد يمكن أن يُنهي النظام بأكمله.

1. ما ستتعلمه


2. قصة مطور حقيقي

(1) نقطة الألم: الاستثناءات غير المعالجة تُسقط المعالجة الدفعية في منتصفها

أثناء معالجة ملايين الطلبات، تعطل خط أنابيب البيانات الخاص ببوب بسبب سطر CSV مشوه تسبب في FormatException. فُقدت جميع السجلات الـ 800,000 التي تمت معالجتها، مما تطلب إعادة التشغيل بالكامل. والأسوأ أن رسالة الخطأ كانت تعرض فقط "FormatException" بدون رقم سطر أو سياق، واستغرق بوب 4 ساعات لتحديد المشكلة.

(2) حل معالجة الاستثناءات

استخدم try-on-catch-finally لالتقاط استثناءات محددة، وفئات استثناءات مخصصة لحمل معلومات السياق، و finally لضمان تحرير الموارد.

DART
try {
  final records = await parseCsvFile(path);
  await processRecords(records);
} on FormatException catch (e) {
  log.error('CSV parse error: ${e.message} at line ${e.offset}');
  // تخطي السجلات المشوهة، متابعة المعالجة
} on TimeoutException {
  log.error('API timeout, retrying...');
  await retryWithBackoff();
} finally {
  await closeResources();
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.

(3) الفوائد


3. Exception مقابل Error

(1) الفرق الدلالي

100%
flowchart TD
  A[Throwable] --> B[Error<br/>قابل للاسترداد: لا]
  A --> C[Exception<br/>قابل للاسترداد: نعم]
  B --> B1[OutOfMemoryError]
  B --> B2[StackOverflowError]
  C --> C1[FormatException]
  C --> C2[TimeoutException]
  C --> C3[IOException]
  C --> C4[استثناء مخصص]
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.
الجانب Error Exception
قابلية الاسترداد غير قابل للاسترداد قابل للاسترداد
هل يجب التقاطه لا نعم
ناتج عن VM / Runtime كود التطبيق
مثال StackOverflowError FormatException
⚠️ ملاحظة: لا تلتقط Errors. الـ Error يشير إلى أن حالة البرنامج فاسدة؛ التقاطه ومتابعة التنفيذ قد يؤدي إلى مشاكل أكثر خطورة. التقط Exceptions فقط.


4. try / on / catch / finally

(1) الصياغة الكاملة

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.

: try-catch الأساسي

DART
void main() {
  try {
    final result = int.parse('abc');
    print(result);
  } on FormatException catch (e) {
    print('Format error: ${e.message}');
  } catch (e) {
    print('Unexpected error: $e');
  }
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.

: الفرق بين on و catch

DART
void main() {
  // on - يلتقط نوعاً محدداً، بدون الوصول إلى كائن الاستثناء
  try {
    int.parse('not a number');
  } on FormatException {
    print('Caught FormatException (no details needed)');
  }

  // on + catch - يلتقط نوعاً محدداً مع الوصول إلى الاستثناء
  try {
    int.parse('not a number');
  } on FormatException catch (e) {
    print('Caught: ${e.message}');
  }

  // catch مع تتبع المكدس
  try {
    int.parse('not a number');
  } on FormatException catch (e, stackTrace) {
    print('Error: $e');
    print('Stack: $stackTrace');
  }
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.

: كتلة finally

DART
import 'dart:io';

void main() async {
  File? file;
  try {
    file = File('orders.csv');
    final content = await file.readAsString();
    print('Read ${content.length} characters');
  } on FileSystemException catch (e) {
    print('File error: ${e.message}');
  } finally {
    // يتم تنفيذه دائماً - حتى في حالة return أو throw
    print('Cleanup: file handle released');
  }
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.
البند الغرض يمكن تكراره الترتيب
try يلف الكود الذي قد يرمي استثناءً 1 أولاً
on Type يلتقط نوعاً محدداً متعدد بعد try
catch (e) يلتقط أي استثناء 1 بعد on
finally يُنفذ دائماً 1 أخيراً

5. فئات الاستثناءات المخصصة

(1) مبادئ تصميم فئات الاستثناءات

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.

: فئات الاستثناءات المخصصة

DART
// الاستثناء الأساسي لخط أنابيب البيانات
class PipelineException implements Exception {
  final String message;
  final String? source;
  final int? lineNumber;

  PipelineException(this.message, {this.source, this.lineNumber});

  @override
  String toString() => 'PipelineException: $message'
      '${source != null ? " (source: $source)" : ""}'
      '${lineNumber != null ? " at line $lineNumber" : ""}';
}

// أنواع استثناءات محددة
class DataFormatException extends PipelineException {
  final String fieldName;
  final String invalidValue;

  DataFormatException({
    required this.fieldName,
    required this.invalidValue,
    required super.message,
    super.source,
    super.lineNumber,
  });

  @override
  String toString() => 'DataFormatException: $message '
      '(field: $fieldName, value: "$invalidValue")';
}

class NetworkTimeoutException extends PipelineException {
  final Duration timeout;
  final String endpoint;

  NetworkTimeoutException({
    required this.timeout,
    required this.endpoint,
    super.message = 'Request timed out',
  }) : super(message);

  @override
  String toString() => 'NetworkTimeout: ${timeout.inSeconds}s '
      'timeout on $endpoint';
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.

: تصميم رمز الخطأ

DART
enum ErrorCode {
  fileNotFound('E001', 'File not found'),
  invalidFormat('E002', 'Invalid data format'),
  networkTimeout('E003', 'Network request timed out'),
  authFailed('E004', 'Authentication failed'),
  rateLimitExceeded('E005', 'Rate limit exceeded');

  final String code;
  final String description;

  const ErrorCode(this.code, this.description);
}

class CodedException extends PipelineException {
  final ErrorCode errorCode;

  CodedException(this.errorCode, {String? detail})
      : super('${errorCode.code}: ${errorCode.description}'
            '${detail != null ? " - $detail" : ""}');

  @override
  String toString() => '[$errorCode] $message';
}

void main() {
  try {
    throw CodedException(ErrorCode.invalidFormat, detail: 'amount field is not a number');
  } on CodedException catch (e) {
    print(e);  // [ErrorCode.invalidFormat] E002: Invalid data format - amount field is not a number
  }
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.

6. rethrow وسلسلة الاستثناءات

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.

: rethrow

DART
double parseAmount(String input) {
  try {
    return double.parse(input);
  } on FormatException catch (e) {
    // سجّل وأعد الرمي - لا تبتلع الاستثناء
    print('Failed to parse amount: "$input"');
    rethrow;  // يحافظ على تتبع المكدس الأصلي
  }
}

void main() {
  try {
    final amount = parseAmount('not_a_number');
  } on FormatException {
    print('Caught rethrown exception');
  }
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.

: سلسلة الاستثناءات

DART
class ChainedException implements Exception {
  final String message;
  final Exception? innerException;

  ChainedException(this.message, {this.innerException});

  @override
  String toString() {
    var result = 'ChainedException: $message';
    if (innerException != null) {
      result += '\n  Caused by: $innerException';
    }
    return result;
  }
}

Future<double> fetchOrderAmount(String orderId) async {
  try {
    // محاكاة استدعاء API
    throw FormatException('Invalid JSON response');
  } on FormatException catch (e) {
    throw ChainedException(
      'Failed to fetch order $orderId',
      innerException: e,
    );
  }
}

void main() async {
  try {
    await fetchOrderAmount('ORD-001');
  } on ChainedException catch (e) {
    print(e);
  }
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.

7. سيناريو بوب: معالجة استثناءات خط أنابيب البيانات

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.

: تدفق معالجة الاستثناءات الكامل

DART
import 'dart:async';

// استثناءات مخصصة
class PipelineException implements Exception {
  final String message;
  PipelineException(this.message);
  @override
  String toString() => 'PipelineException: $message';
}

class CsvParseException extends PipelineException {
  final int lineNumber;
  CsvParseException(String message, this.lineNumber) : super(message);
  @override
  String toString() => 'CsvParseException: $message (line $lineNumber)';
}

// محلل CSV آمن مع معالجة الاستثناءات
List<Map<String, String>> parseCsv(String content) {
  final lines = content.split('\n');
  if (lines.isEmpty) throw PipelineException('Empty CSV content');

  final headers = lines[0].split(',');
  final records = <Map<String, String>>[];

  for (var i = 1; i < lines.length; i++) {
    final line = lines[i].trim();
    if (line.isEmpty) continue;

    try {
      final values = line.split(',');
      if (values.length != headers.length) {
        throw CsvParseException(
          'Column count mismatch: expected ${headers.length}, got ${values.length}',
          i + 1,
        );
      }
      final record = <String, String>{};
      for (var j = 0; j < headers.length; j++) {
        record[headers[j].trim()] = values[j].trim();
      }
      records.add(record);
    } on CsvParseException {
      rethrow;
    } catch (e) {
      throw CsvParseException('Unexpected error: $e', i + 1);
    }
  }
  return records;
}

Future<void> processData(String csvContent) async {
  List<Map<String, String>>? records;

  try {
    records = parseCsv(csvContent);
    print('Parsed ${records.length} records');

    // محاكاة استدعاء API مع مهلة
    await Future.delayed(const Duration(seconds: 1));
    print('Data submitted successfully');
  } on CsvParseException catch (e) {
    print('Parse error: $e - skipping malformed records');
  } on TimeoutException catch (e) {
    print('Network timeout: $e - will retry later');
  } on PipelineException catch (e) {
    print('Pipeline error: $e');
  } finally {
    print('Cleanup: resources released');
  }
}

void main() async {
  final csv = 'id,amount,status\nORD-001,1500,completed\nORD-002,50,pending\nORD-003,bad_data,completed';
  await processData(csv);

  print('\n--- Test with malformed CSV ---');
  final badCsv = 'id,amount\nORD-001,1500,completed';  // عدد أعمدة خاطئ
  await processData(badCsv);
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.

8. المثال الكامل: معالجة بيانات متينة لخط أنابيب البيانات

DART
// ============================================
// معالجة بيانات متينة لخط أنابيب البيانات
// معالجة استثناءات كاملة مع استثناءات مخصصة
// ============================================

import 'dart:async';

// رموز الأخطاء
enum PipelineError {
  fileNotFound('E001', 'File not found'),
  invalidFormat('E002', 'Invalid data format'),
  networkTimeout('E003', 'Network timeout'),
  validationFailed('E004', 'Validation failed');

  final String code;
  final String label;
  const PipelineError(this.code, this.label);
}

class PipelineException implements Exception {
  final PipelineError error;
  final String detail;
  final Exception? cause;

  PipelineException(this.error, {this.detail = '', this.cause});

  @override
  String toString() => '[${error.code}] ${error.label}'
      '${detail.isNotEmpty ? ": $detail" : ""}'
      '${cause != null ? " (caused by: $cause)" : ""}';
}

// طلب مع التحقق
class Order {
  final String id;
  final double amount;
  final String status;

  Order({required this.id, required this.amount, required this.status}) {
    if (id.isEmpty) {
      throw PipelineException(PipelineError.validationFailed, detail: 'Order ID is empty');
    }
    if (amount <= 0) {
      throw PipelineException(PipelineError.validationFailed, detail: 'Amount must be positive: $amount');
    }
  }

  @override
  String toString() => 'Order($id, \$${amount.toStringAsFixed(2)}, $status)';
}

// محلل طلبات متين
class OrderParser {
  final List<PipelineException> _errors = [];
  int parsed = 0;
  int skipped = 0;

  List<PipelineException> get errors => List.unmodifiable(_errors);

  Order? tryParse(Map<String, dynamic> data) {
    try {
      final order = Order(
        id: (data['id'] ?? '') as String,
        amount: (data['amount'] as num).toDouble(),
        status: (data['status'] ?? 'unknown') as String,
      );
      parsed++;
      return order;
    } on PipelineException catch (e) {
      _errors.add(e);
      skipped++;
      return null;
    } on TypeError catch (e) {
      _errors.add(PipelineException(
        PipelineError.invalidFormat,
        detail: 'Type mismatch in record: $e',
      ));
      skipped++;
      return null;
    }
  }

  void printReport() {
    print('Parsed: $parsed, Skipped: $skipped');
    if (_errors.isNotEmpty) {
      print('Errors:');
      for (final e in _errors) {
        print('  $e');
      }
    }
  }
}

void main() {
  final rawData = <Map<String, dynamic>>[
    {'id': 'ORD-001', 'amount': 1500.0, 'status': 'completed'},
    {'id': '', 'amount': 500.0, 'status': 'pending'},           // غير صالح: معرف فارغ
    {'id': 'ORD-003', 'amount': -50.0, 'status': 'completed'},  // غير صالح: مبلغ سالب
    {'id': 'ORD-004', 'amount': 'not_a_number', 'status': 'pending'}, // غير صالح: نوع خاطئ
    {'id': 'ORD-005', 'amount': 3200.0, 'status': 'completed'},
  ];

  final parser = OrderParser();
  final validOrders = <Order>[];

  for (final data in rawData) {
    final order = parser.tryParse(data);
    if (order != null) validOrders.add(order);
  }

  print('=== DataPipeline Processing Report ===');
  parser.printReport();

  print('\nValid Orders:');
  for (final order in validOrders) {
    print('  $order');
  }

  final totalRevenue = validOrders.fold<double>(0, (s, o) => s + o.amount);
  print('\nTotal Revenue: \$${totalRevenue.toStringAsFixed(2)} USD');
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل في DartPad محلي أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x، وقد تختلف النتائج قليلاً حسب إصدار SDK.

الإخراج:

TEXT 📖 للعرض فقط
=== DataPipeline Processing Report ===
Parsed: 3, Skipped: 2
Errors:
  [E004] Validation failed: Order ID is empty
  [E004] Validation failed: Amount must be positive: -50.0
  [E002] Invalid data format: Type mismatch in record: ...

Valid Orders:
  Order(ORD-001, $1500.00, completed)
  Order(ORD-005, $3200.00, completed)

Total Revenue: $4700.00 USD

❓ أسئلة شائعة

س: هل لدى Dart استثناءات محددة (checked exceptions)؟ ج: لا. جميع الاستثناءات في Dart غير محددة (unchecked)؛ المترجم لا يفرض الإعلان عنها أو التقاطها. هذا يوفر مرونة لكنه يتطلب من المطورين التعامل مع الاستثناءات بوعي.

س: ما الفرق بين on و catch؟ ج: on Type يلتقط نوع استثناء محدداً دون ربط متغير (ما لم تتم إضافة catch). catch (e) يلتقط أي استثناء ويربطه بمتغير. عادةً، يُستخدم المزيج on Type catch (e).

س: ما فائدة catch و rethrow؟ ج: catch يتيح لك التسجيل، تنفيذ التنظيف، إلخ، بعد التقاط الاستثناء. ثم استخدم rethrow لإعادة رمي نفس الاستثناء حتى تتمكن الطبقات العليا من معالجته. rethrow يحافظ على تتبع المكدس الأصلي، وهو أفضل من throw e.

س: متى تُنفذ كتلة finally؟ ج: تُنفذ كتلة finally دائماً، بصرف النظر عما إذا كان قد تم رمي استثناء في كتلة try، أو ما إذا تم التقاطه، أو ما إذا كانت هناك جملة return. الاستثناء الوحيد هو إذا تم إنهاء البرنامج (مثلاً عبر SIGKILL).

س: ما الذي يجب أن ترث منه الاستثناءات المخصصة؟ ج: يُنصح بـ implement Exception بدلاً من extend Exception. implements أكثر مرونة، ولا يقيدك بالوراثة الأحادية. هذا هو الأسلوب الذي توصي به إرشادات Dart الرسمية.

س: هل معالجة الاستثناءات تؤثر على الأداء؟ ج: كتلة try-catch نفسها لها عبء قريب من الصفر (لا تضيف آلة Dart الافتراضية تعليمات إضافية داخل كتل try). ومع ذلك، فإن إنشاء الاستثناءات ورميها له تكلفة، ولا ينبغي استخدامها كآلية تحكم عادية في التدفق.

س: كيف أتجنب ابتلاع الاستثناءات؟ ج: كتلة catch الفارغة هي علامة على كود سيء. على الأقل سجّل الاستثناء، أو استخدم rethrow. إذا كان يجب عليك تجاهله، استخدم catch (_) {} وأضف تعليقاً يوضح السبب.


📖 ملخص


📝 تمارين

  1. أساسي (صعوبة ⭐): اكتب دالة safeParseInt(String s) تستخدم int.parse داخل كتلة try-catch. إذا فشل التحليل، أرجع 0 بدلاً من رمي استثناء. اختبر بـ 3 حالات.
  2. متوسط (صعوبة ⭐⭐): عرّف فئة استثناء مخصص DataFormatException تحمل اسم الحقل والقيمة غير الصالحة ورقم السطر. اكتب دالة تحليل CSV ترمي هذا الاستثناء عند أخطاء التنسيق، والتقطها في موقع الاستدعاء لطباعة معلومات مفصلة.
  3. تحدي (صعوبة ⭐⭐⭐): نفّذ دالة طلب HTTP مع آلية إعادة محاولة تدعم عدداً مخصصاً من المحاولات واستراتيجية تراجع. أعد المحاولة عند TimeoutException، وبعد تجاوز عدد المحاولات، ارمِ استثناءً مجمعاً يحتوي على جميع استثناءات المحاولات الفردية.

← الدرس السابق | الدرس التالي →

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%