ما هو try-catch ؟
السلام عليكم ورحمة الله وبركاته
المقدمة
عند كتابة أي كود قد تحدث بعض المشاكل الغير متوقعة أو أخطاء نحن نتوقع حدوثها لكن نريد أن نتفاداها
هذه الأخطاء متنوعة جدًا لذلك يجب علينا أن نكون مستعدين للتعامل معها
ونريد وسيلة تساعدنا على التعامل مع هذه الأخطاء بشكل أنيق ومنظم ولا تزعج المستخدمين
الآن لنفترض أننا قمنا بكتابة كود بسيط جدًا يقوم بقسمة رقمين
// بيانات حصلنا عليها من المستخدم
float x = 10;
float y = 2;
float result = x / y;
cout << result; // سيطبع 5
هنا كود بسيط يقوم بأخذ بيانات من المستخدم ثم يقوم بقسمة الرقم الأول على الرقم الثاني ثم يقوم بطباعة الناتج
هل ترى أي مشكلة في هذا الكود؟
بديهيًا ستقول لي لا، لكن هناك مشكلة كبيرة في هذا الكود
أنت تأخذ بيانات من المستخدم والمشكلة تكمن هنا
أنت لا تعرف كيف سيتصرف المستخدم فمثلًا قد تطلب منه إدخال رقم معين ثم تجده ادخل لك أنا أحب البطاطس
أنا لا أمازحك، أنت حقًا لا يجب أن تثق في المستخدمين
لذا عندما تطلب منهم شيء توقع أنهم سيدخلون لك أي شيء وعكس ما تطلبه
في هذا الكود البسيط الذي عرضناه يمكن أن يدخل المستخدم أي شيء ويمكنه أن يدخل الرقم صفر وهنا ستحدث مشكلة لأنه لا يمكن قسمة عدد على الصفر
float x = 10;
float y = 0; // هنا المستخدم قام بإدخال الرقم صفر
float result = x / y;
cout << result; // inf سيطبع
هنا في أي لغة برمجة عندما تقوم بقسمة عدد على الصفر ستحدث مشكلة سواء سيتوقف البرنامج عن العمل
أو سيعطيك نتيجة غير صحيحة أو سيعطيك رسالة خطأ غير مفهومة للمستخدم
في لغة الـ C++
سيعطيك inf
وهو يعني Infinity
أي أن الناتج لا نهاية له
وفي لغة الـ TypeScript
سيعطيك Infinity
وهو نفس الشيء
أما في لغات أخرى مثل Python
رسالة طويلة وعريضة تقول لك أنه لا يمكن قسمة عدد على الصفر
ثم يتوقف البرنامج عن العمل تمامًا
ERROR!
Traceback (most recent call last):
File "<main.py>", line 4, in <module>
ZeroDivisionError: division by zero
لا أظن أنك تريد أن تقوم بعمل برنامج آلة حاسبة كأول برنامج لك بعد تعلمك البرمجة
ثم تذهب وتعرضه على أصدقائك بابتسامة كبيرة وعريضة ويقوم أحد أصدقائك بإدخال الرقم صفر ويتوقف البرنامج عن العمل ويعطيه رسالة خطأ غير مفهومة كتلك
ماذا ستفعل ؟ هل ستقول له أنه المخطئ ؟، لا صديقك ليس المخطئ لأنه مستخدم والمستخدم يفعل ما يحلو له لذا على أنت أن تتعامل مع هذه الأخطاء
وستحاول أن تتفاداها لأنها ستسبب لتوقف البرنامج بشكل مفاجئ أمام المستخدم
أو ستعطيه رسالة خطأ غير مفهومة أو ستعطيه نتائج غير صحيحة
هذه الأخطاء تسمى بالـ Exceptions
وهي أخطاء لا يمكن تنبؤ بها وتحدث بشكل غير متوقع
والمشكلة أن غالبًا ما تحدث في الـ runtime
أي أثناء تنفيذ البرنامج وأثناء استخدام المستخدم للبرنامج
هنا يأتي دور الـ try-catch
ليساعدنا في التعامل مع هذه الأخطاء
وهي وسيلة تسمح لنا بمحاولة تنفيذ الكود الذي قد يتسبب بخطأ، ثم التقاط هذا الخطأ إذا حدث والتعامل معه بشكل مناسب
كيفية استخدام الـ try-catch
الشكل العام لـ try-catch
هو كالتالي:
try {
// الكود الذي قد يسبب خطأ
} catch (نوع الخطأ) {
// كيفية التعامل مع الخطأ
}
نكتب الكود الذي قد يسبب خطأ داخل الـ try
، ثم نكتب داخل الـ catch
الكود الذي سيتعامل مع الخطأ إذا حدث
لاحظ أن الـ catch
لديه أقواس ()
يستقبل بها نوع الخطأ الذي سيتعامل معه والذي سيأتيه من الـ try
دعونا نرى مثالًا عمليًا
try {
float x = 10;
float y = 0; // هنا المستخدم قام بإدخال الرقم صفر
float result = x / y;
cout << result;
} catch (error) {
console.log('حدث خطأ');
}
هنا قمنا بوضع الكود الذي قد يسبب خطأ داخل الـ try
ثم قمنا بوضع الكود الذي سيتعامل مع الخطأ داخل الـ catch
حاليًا نقوم بطباعة حدث خطأ
في معظم اللغات عندما يحدث أي خطأ داخل الـ try
ستقوم اللغة بإلقاء Exception
ثم تلقائيًا سيتم توجيه تنفيذ الكود إلى الـ catch
وسيتم طباعة حدث خطأ
الرسالة التي كتبناها
لقد قلت في معظم اللغات لأن هناك بعض اللغات مثل C++
و Typescript
تسمح لك بقسمة عدد على الصفر وستعطيك الناتج كـ inf
و Infinity
بالتالي هنا في هذا المثال لن تقوم لغة الـ C++
بإلقاء أي Exception
بالتالي لن يتم توجيه التنفيذ إلى الـ catch
ولن يتم طباعة حدث خطأ
بل سيتم طباعة inf
لكن في لغات أخرى مثل Python
ستقوم بإلقاء Exception
وسيتم توجيه التنفيذ إلى الـ catch
وسيتم طباعة حدث خطأ
try:
x = 10
y = 0
result = x / y
print(result)
except:
print('حدث خطأ')
هنا في لغة الـ Python
سيتم طباعة حدث خطأ
لأنها تقوم بإلقاء Exception
عند قسمة عدد على الصفر
لاحظ أن الـ catch
في لغة الـ Python
يسمى except
وليس catch
ولاحظ أيضا لاحظ أن طالمة حدث Exception
في أي مكان داخل الـ try
سيتم توجيه التنفيذ إلى الـ catch
ولن يتم تنفيذ باقي الكود
بمعنى أن السطر print(result)
لن يتم تنفيذه لأنه بعد السطر الذي حدث فيه الخطأ
لنعود إلى مثالنا السابق في لغة الـ C++
قلنا أنها لن تقوم بإلقاء Exception
عند قسمة عدد على الصفر
وهذا سيسبب مشاكل لنا لأننا لا نريد أن نعرض للمستخدم نتيجة غير صحيحة أو inf
لذا يجب علينا أن نقوم نحن بإلقاء الـ Exception
طالما أن اللغة لن تفعل ذلك
لكن كيف يمكننا أن نلقي الـ Exception
بأنفسنا ؟
استخدام throw
جميع اللغات تسمح لك بإلقاء الـ Exception
بنفسك باستخدام الأمر throw
وهو أمر في غاية البساطة يستقبل أي شيء ويقوم بإلقائه كـ Exception
إلى الـ catch
try {
// الكود الذي قد يسبب خطأ
throw 'حدث خطأ'; // Exception إلقاء الـ
// باقي الكود
} catch (error) {
// التعامل مع الخطأ
}
لاحظ أننا قمنا بوضع throw 'حدث خطأ'
داخل الـ try
و throw
هنا استقبلت string
ويمكنها أن تستقبل أي شيء تريد
throw 404; // ✅
throw true; // ✅
throw 'في مشكلة، حمادة أكل البطاطس'; // ✅
دعونا نعود إلى مثالنا السابق ونقوم بإلقاء الـ Exception
بأنفسنا
try {
float x = 10;
float y = 0; // هنا المستخدم قام بإدخال الرقم صفر
if (y == 0) {
string message = "لا يمكن قسمة عدد على الصفر";
throw message;
}
float result = x / y;
cout << result;
} catch (string message) {
cout << message;
}
هنا قمنا بوضع الكود الذي قد يسبب خطأ داخل الـ try
ليقوم بمحاولة تنفيذه
ثم نبدأ بالتحقق من الأخطاء المحتملة التي قد يقوم بها المستخدم
مثلًا هنا قمنا بالتحقق من إذا كان الرقم الثاني يساوي الصفر أم لا
فإذا كان يساوي الصفر سنقوم بإلقاء الـ Exception
بنص لا يمكن قسمة عدد على الصفر
عن طريق throw
ثم بالتالي ستقوم الـ catch
بالتقاط هذا الـ Exception
والتعامل معه بشكل مناسب
لاحظ أننا قمنا بوضع string
داخل الـ catch
لأننا قمنا بإلقاء متغير من نوع string
عن طريق throw
وبالتالي يجب علينا أن نكتب نفس نوع البيانات داخل الـ catch
لكي يتم التعامل معه بشكل صحيح
لذاك لو قمنا بإلقاء int
أو bool
أو أي نوع آخر يجب علينا أن نكتب نفس النوع داخل الـ catch
الآن يمكنك أن تكتب برامجك بشكل أفضل وأكثر أمانًا
وتعرضها على أصدقائك بابتسامة وإذا صديقك بإدخال الرقم صفر مجددًا فلا تقلق لن يتوقف البرنامج هذه المرة ولن يعطيه رسالة خطأ غير مفهومة
بل سيعطيه الرسالة التي كتبتها أنت لا يمكن قسمة عدد على الصفر
وسيعرف أنه المخطئ وليس البرنامج
ويمكنك أن تقول له بكل ثقة أنت المخطئ يا صديقي 😎
لاحظ أن إذا كان هناك أي كود بعد الـ throw
في الـ try
فلن يتم تنفيذه
لأن الـ throw
سيقوم بإلقاء الـ Exception
وسيتم توجيه التنفيذ إلى الـ catch
مباشرة
دعونا نرى مثالًا على الـ throw
في الـ try
لترسيخ الفكرة فقط
try {
int number;
cout << "Enter a positive number: ";
cin >> number;
if (number < 0) {
throw number;
}
cout << "The number is: " << number;
} catch (int number) {
cout << "The number " << number << " is not a positive number";
}
لنفترض هنا لدينا برنامج يطلب من المستخدم إدخال رقم موجب
ثم يقوم بطباعة الرقم إذا كان موجبًا
وإذا كان سالبًا سيقوم بإلقاء الـ Exception
بالرقم الذي قام بإدخاله المستخدم throw number
وطالما قمنا بإلقاء int
فسيتم توجيه التنفيذ إلى الـ catch
الذي يتعامل مع الـ int
وسيتم طباعة The number 5 is not a positive number
التعامل مع أكثر من نوع من الـ Exception
يمكنك أن تكتب أكثر من catch
للتعامل مع أكثر من نوع من الـ Exception
هكذا سيتم تنفيذ الـ catch
الذي يتعامل مع الـ Exception
الذي حدث
try {
// الكود الذي قد يسبب خطأ
throw 400; // int من نوع Exception إلقاء الـ
// باقي الكود
} catch (int number) {
// int من نوع Exception التعامل مع أي
} catch (string message) {
// string من نوع Exception التعامل مع أي
}
هنا قمنا بوضع catch
للـ int
و catch
للـ string
وبالتالي سيتم تنفيذ الـ catch
الذي يتعامل مع الـ Exception
الذي حدث
فلو قامت الـ throw
بإلقاء int
سيتم تنفيذ الـ catch
الذي يتعامل مع الـ int
وإذا قامت الـ throw
بإلقاء string
سيتم تنفيذ الـ catch
الذي يتعامل مع الـ string
لكن ماذا إذا أردنا إلقاء Exception
من نفس النوع لكن نريد نتعامل معها بشكل مختلف ؟
فبمعنى أننا لدينا الآن catch
واحد للـ int
ونحن نريد أن نتعامل مع أكثر من حالة للـ int
بشكل مختلف
فمثلا:
- إذا كان الرقم سالبًا نريد أن نطبع
The number is negative
- إذا كان الرقم صفر نريد أن نطبع
The number is zero
- إذا كان الرقم موجبًا نريد أن نطبع
The number is positive
- وهناك حالة عندما تحدث نريد أن نقوم بشيء أخر غير الطباعة
- وهكذا ...
كيف يمكننا أن نفعل ذلك ؟ ونحن فقط نملك catch
واحد للـ int
؟
هنا يمكننا أن نبتكر بعض الأفكار للتعامل مع هذه الحالات
فمثلًا لما لا نجعل الرقم الذي تستقبله الـ catch
يحدد نوع الحالة ؟
فمثلا:
- إذا أرسلنا
100
فهذا يعني أن الرقم سالب - إذا أرسلنا
200
فهذا يعني أن الرقم صفر - إذا أرسلنا
300
فهذا يعني أن الرقم موجب - إذا أرسلنا
400
فهذا يعني حالة أخرى فسنقوم بتنفيذ شيء آخر - إذا أرسلنا
500
فهذا يعني حالة أخرى فسنقوم بتنفيذ شيء آخر - وهكذا ...
هذه الأرقام ليست معيارية ويمكنك أن تختار أي رقم تريده لتمثل حالة معينة
أنها فقط أرقام رمزية أنت تحددها وتعرف ماذا تعني
هكذا يمكنك أن تتعامل مع أكثر من حالة لنفس الـ Exception
بشكل مختلف
try {
throw 100; // int من نوع Exception إلقاء الـ
} catch (int number) {
switch (number) {
case 100:
cout << "The number is negative";
break;
case 200:
cout << "The number is zero";
break;
case 300:
cout << "The number is positive";
break;
case 400:
// حالة أخرى
break;
case 500:
// حالة أخرى
break;
default:
cout << "حدث خطأ غير معلوم";
}
}
أنت في البرمجة تحتاج فقط إلى الإبداع والتفكير في كيفية حل المشكلة بالمعطيات التي تملكها
وبحسب مميزات وعيوب اللغة التي تستخدمها
الختام
أظن أن هذا القدر يكفي لكي تبدأ في استخدام الـ try-catch
في برامجك
وتتعامل مع الأخطاء بشكل أفضل وأكثر أمانًا
وأنت ستستخدمها بكثر في المشاريع الكبيرة والبرامج الكبيرة التي تكتبها وحتى تعمل مع فريق في مشروع حقيقي سيستخدمه أشخاص آخرون
ستجد نفسك تستخدم الـ try-catch
بكثرة لأنها تساعدك في التعامل مع الأخطاء بشكل أفضل وأكثر منظم لكي تعرض للمستخدم رسائل خطأ مفهومة
هناك أمور أخرى كنت أود أن أتحدث عنها مثل لكن لا أستطيع لأننا ما زلنا في بداية الطريق
هناك أمور قد أكون قد نسيتها أو لم أتحدث عنها وهذا جيد لكي تبدأ أنت في البحث عنها وتتعلمها بنفسك
لا يوجد شخص أومصدر سيعلمك كل شيء عن البرمجة ودائمًا ستجد شيء جديد تتعلمه
الأمور التي لم تتعلمها ستتعلمها تدريجيًا مع الوقت ومع الخبرة وستخطئ كثيرًا وستتعلم من أخطائك
وستصبح مبرمجًا أفضل وأكثر خبرة وستكتب برامج أفضل وأكثر أمانًا