ما هو try-catch ؟

السلام عليكم ورحمة الله وبركاته

وقت القراءة: ≈ 5 دقائق

المقدمة

عند كتابة أي كود قد تحدث بعض المشاكل الغير متوقعة أو أخطاء نحن نتوقع حدوثها لكن نريد أن نتفاداها
هذه الأخطاء متنوعة جدًا لذلك يجب علينا أن نكون مستعدين للتعامل معها
ونريد وسيلة تساعدنا على التعامل مع هذه الأخطاء بشكل أنيق ومنظم ولا تزعج المستخدمين

الآن لنفترض أننا قمنا بكتابة كود بسيط جدًا يقوم بقسمة رقمين

// بيانات حصلنا عليها من المستخدم
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 بشكل مختلف
فمثلا:

كيف يمكننا أن نفعل ذلك ؟ ونحن فقط نملك catch واحد للـ int ؟

هنا يمكننا أن نبتكر بعض الأفكار للتعامل مع هذه الحالات
فمثلًا لما لا نجعل الرقم الذي تستقبله الـ catch يحدد نوع الحالة ؟

فمثلا:

هذه الأرقام ليست معيارية ويمكنك أن تختار أي رقم تريده لتمثل حالة معينة
أنها فقط أرقام رمزية أنت تحددها وتعرف ماذا تعني

هكذا يمكنك أن تتعامل مع أكثر من حالة لنفس الـ 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 بكثرة لأنها تساعدك في التعامل مع الأخطاء بشكل أفضل وأكثر منظم لكي تعرض للمستخدم رسائل خطأ مفهومة


هناك أمور أخرى كنت أود أن أتحدث عنها مثل لكن لا أستطيع لأننا ما زلنا في بداية الطريق
هناك أمور قد أكون قد نسيتها أو لم أتحدث عنها وهذا جيد لكي تبدأ أنت في البحث عنها وتتعلمها بنفسك

لا يوجد شخص أومصدر سيعلمك كل شيء عن البرمجة ودائمًا ستجد شيء جديد تتعلمه الأمور التي لم تتعلمها ستتعلمها تدريجيًا مع الوقت ومع الخبرة وستخطئ كثيرًا وستتعلم من أخطائك
وستصبح مبرمجًا أفضل وأكثر خبرة وستكتب برامج أفضل وأكثر أمانًا