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

Null Safety في Dart: الأمان من القيم الفارغة

الدرس 3 من 14 · Dart

من أشهر أسباب انهيار التطبيقات في العالم خطأ بسيط: محاولة استخدام قيمة غير موجودة. مخترع هذه الفكرة نفسه سمّاها «خطأ المليار دولار» لكثرة ما سببته من مشاكل. Dart تحل هذه المشكلة بميزة اسمها Null Safety (الأمان من القيم الفارغة)، وهي من أهم ما يميزها. في هذا الدرس ستفهم الفكرة وتتعلم أدواتها.

null تعني «لا قيمة». ليست صفراً، وليست نصاً فارغاً، بل غياب القيمة تماماً.

تخيّل خانة رقم الهاتف في استمارة: بعض الناس يكتبون رقمهم، وبعضهم يتركها فارغة. الخانة الفارغة ليست الرقم صفر، بل «لا يوجد رقم». هذه هي null.

المشكلة تظهر عندما تحاول استخدام قيمة غير موجودة: كأن تطلب من البرنامج «اتصل بالرقم المكتوب في الخانة»، والخانة فارغة! في لغات كثيرة، هذا يجعل التطبيق ينهار فجأة أمام المستخدم.

الحل: الأنواع لا تقبل null افتراضياً

Section titled “الحل: الأنواع لا تقبل null افتراضياً”

في Dart، أي متغير عادي لا يمكن أن يكون null:

String name = 'سارة';
name = null; // خطأ! المحرر يرفض هذا السطر

هذا يعني أنك إذا رأيت متغيراً من نوع String، فأنت متأكد 100% أن فيه نصاً، ويمكنك استخدامه بأمان. المحرر يكشف المشكلة وأنت تكتب الكود، لا والمستخدم يستخدم التطبيق.

الأنواع التي تقبل null: علامة ?

Section titled “الأنواع التي تقبل null: علامة ?”

إذا كانت القيمة قد تكون غير موجودة فعلاً، أضف ? بعد النوع:

String? nickname;
print(nickname);
nickname = 'سوسو';
print(nickname);
الناتج
null
سوسو
  • String? تعني: «نص، أو null».
  • المتغير الذي يقبل null وليست له قيمة، تكون قيمته null تلقائياً.

والآن، Dart لن تسمح لك باستخدام nickname مباشرة في أي شيء يحتاج نصاً مؤكداً، حتى تتعامل مع احتمال أنه فارغ. هذه هي الحماية.

العامل ??: قيمة احتياطية

Section titled “العامل ??: قيمة احتياطية”

«إذا كانت القيمة null، فاستخدم هذه بدلاً منها»:

String? username;
print('أهلاً يا ${username ?? 'زائر'}');
الناتج
أهلاً يا زائر

ومعه أخوه ??=: «أسند القيمة فقط إذا كان المتغير null الآن»:

int? stock;
stock ??= 10;
print(stock);
الناتج
10

إذا أردت خاصية أو دالة من متغير قد يكون null، استخدم ?. بدل النقطة العادية:

String? email;
print(email?.length);
print(email?.length);
الناتج
null
16

اقرأها: «إذا كان email موجوداً، أعطني طوله، وإلا أعطني null بهدوء». بدون انهيار.

الطريقة الأوضح غالباً: افحص بنفسك. والجميل أن Dart تتذكر نتيجة الفحص:

String? title;
if (title != null) {
print(title.length); // هنا Dart تعرف أن title ليس null
} else {
print('لا يوجد عنوان');
}
الناتج
لا يوجد عنوان

داخل if (title != null)، تتعامل Dart مع title كأنه String عادي. هذه الميزة اسمها ترقية النوع (Type Promotion). ومثال عملي في دالة:

String describe(String name, int? age) {
if (age == null) {
return '$name: العمر غير معروف';
}
return '$name: $age سنة';
}
void main() {
print(describe('سارة', null));
print(describe('عمر', 15));
}
الناتج
سارة: العمر غير معروف
عمر: 15 سنة

العلامة ! بعد المتغير تقول لـ Dart: «أنا متأكد أن هذه القيمة ليست null، صدّقني»:

String? code = 'A1';
String sure = code!;
print(sure);
الناتج
A1

لكن إذا كنت مخطئاً والقيمة null فعلاً، ينهار البرنامج بخطأ Null check operator used on a null value.

أحياناً لا تستطيع إعطاء المتغير قيمة لحظة تعريفه، لكنك متأكد أنك ستعطيه قيمة قبل استخدامه:

late String message;
message = 'تم التحميل';
print(message);
الناتج
تم التحميل

late وعد منك: «سأملأ هذا المتغير قبل أن أقرأه». وإذا أخلفت الوعد وقرأته قبل إسناده، يظهر خطأ أثناء التشغيل. ستراها في Flutter أحياناً مع المتغيرات التي تُجهَّز عند بداية الشاشة.

اكتب دالة greet(String? name) تُرجع «أهلاً يا …» بالاسم، أو «أهلاً يا ضيف» إذا كان الاسم null. جرّبها بثلاث طرق: بـ if، ثم بـ ??، ولاحظ كيف أصبح الكود أقصر.

String greet(String? name) => 'أهلاً يا ${name ?? 'ضيف'}';
void main() {
print(greet('منى'));
print(greet(null));
}
الناتج المتوقع
أهلاً يا منى
أهلاً يا ضيف
  • إسناد null لنوع عادي: int count = null; خطأ. استخدم int? إذا كان الغياب ممكناً فعلاً.
  • الإفراط في استخدام !: يعيد مشاكل الانهيار التي جاءت Null Safety لتمنعها.
  • جعل كل شيء يقبل null: إضافة ? لكل متغير «احتياطاً» تجبرك على فحوص في كل مكان. استخدم ? فقط عندما يكون الغياب جزءاً حقيقياً من المعنى.

سؤال

ما ناتج String? city; print(city ?? 'غير محدد');؟

سؤال

ماذا يحدث عند تنفيذ x! إذا كانت قيمة x تساوي null؟

  • null تعني غياب القيمة، والأنواع العادية في Dart لا تقبلها أبداً.
  • String? نوع يقبل null، ويجبرك على التعامل مع احتمال الغياب.
  • ?? لقيمة احتياطية، و ??= للإسناد عند الغياب، و ?. للوصول الآمن.
  • الفحص بـ if (x != null) يرقّي النوع داخل الشرط.
  • ! تلغي الحماية فاستخدمها بحذر، و late وعد بإسناد القيمة قبل قراءتها.