في العالم المثالي، يعمل كل شيء على نحو مثالي. لكن في العالم الحقيقي، قد تسوء الأمور، وطريقة تعاملنا مع هذه المواقف مهمة لضمان أن تكون برمجياتنا متينة وموثوقة. الاستثناءات مفهوم بالغ الأهمية في البرمجة، فهو يسمح لنا بالتعامل مع الأخطاء والمواقف غير المتوقعة بسلاسة.
رفع خطأ، إذا لم يُعالج، يوقف البرنامج ويطبع رسالة خطأ. في معظم الحالات، لا تريد أن يتوقف برنامجك عند حدوث خطأ، بل تريد التعامل مع الخطأ ومواصلة تشغيل البرنامج.
في Crystal، تُرفع الاستثناءات باستخدام الكلمة المفتاحية raise، ويمكن إما تمرير String أو كائن Exception إليها.
إذا لم يُعالج، سيتوقف البرنامج ويطبع رسالة الخطأ.
raise "This is an error"
توجد عدة استثناءات مدمجة في Crystal، مثل ArgumentError وIndexError وKeyError وIOError وSystemCallError وTypeError وZeroDivisionError وغيرها الكثير.
تتطلب منك تمرير رسالة إلى الاستثناء.
raise ArgumentError.new("This is an argument error")
لا نريد أن يتعطل برنامجنا عند رفع استثناء.
لذلك، عندما نعلم أن جزءًا من الكود عُرضة للخطأ، يمكننا تغليفه في كتلة begin والتقاط الاستثناء بكتلة rescue.
تحدد كتلة begin بداية الكود الذي قد يرفع استثناءً، وتتعامل كتلة rescue مع الاستثناء.
begin
raise "This is an error"
rescue
puts "An error occurred!"
end
يمكن أيضًا تحديد كتلة rescue باستخدام متغير للحصول على كائن الاستثناء.
begin
raise "This is an error"
rescue ex
puts "An error occurred: #{ex.message}"
end
يمكن أيضًا تحديد كتلة rescue بنوع استثناء محدد لالتقاط ذلك الاستثناء فقط.
begin
raise ArgumentError.new("This is an argument error")
rescue ArgumentError
puts "An argument error occurred!"
end
# or
begin
raise ArgumentError.new("This is an argument error")
rescue ex : ArgumentError
puts "An argument error occurred: #{ex.message}"
end
يمكن استخدام عدة كتل rescue للتعامل مع أنواع مختلفة من الاستثناءات.
في المثال أدناه، ستلتقط كتلة rescue الأولى استثناء ArgumentError، وستلتقط كتلة rescue الثانية أي استثناء آخر.
begin
raise ArgumentError.new("This is an argument error")
rescue ArgumentError
puts "An argument error occurred!"
rescue
puts "An error occurred!"
end
يمكن أن تحتوي كتلة begin أيضًا على كتلة else، تُنفَّذ إذا لم يُرفع أي استثناء.
begin
puts "No error occurred"
rescue
puts "An error occurred!"
else
puts "No error occurred"
end
أخيرًا، توجد كتلة ensure تُنفَّذ دائمًا، بغض النظر عما إذا كان قد رُفع استثناء.
begin
raise "This is an error"
rescue
puts "An error occurred!"
ensure
puts "This is always executed"
end
بعض الطرق لها نسختان، واحدة مع ! والأخرى بدونه.
قد يعني هذا شيئين مختلفين.
أحدهما أن الطريقة تُغيّر الكائن، والآخر أن الطريقة يمكن أن ترفع استثناءً.
لكن هناك أيضًا اصطلاح آخر حول إنهاء الطريقة بـ ? ذُكر في مفهوم القيمة المنطقية.
بعض الطرق ترفع استثناءً افتراضيًا، لكن لديها أيضًا نسخة تنتهي بـ ? وتُرجع nil بدلًا من رفع استثناء.
يكون هذا مثاليًا عندما تريد تجنب رفع خطأ. قد يفيد هذا الأداء لأنه لا يضطر إلى إنشاء تتبّع للمكدس، وإذا أُعدّ بشكل صحيح، فقد يجعل الكود أكثر أمانًا.
يمكنك أيضًا إنشاء استثناءاتك الخاصة عبر الوراثة من الصنف Exception.
وبذلك، يمكنك اختياريًا تجاوز طريقة initialize لتعيين رسالة الاستثناء.
يمكن فعل ذلك بإسناد الرسالة إلى متغير نسخة باسم @message.
class MyException < Exception
def initialize
@message = "This is my exception"
end
end
raise MyException.new
حان أخيرًا اليوم الذي انتظرته طويلًا، وأصبحت الآن المالك الفخور بمزرعة جميلة في جبال الألب.
ما زلت لا تحب الاستيقاظ مبكرًا جدًا في الصباح لإطعام أبقارك.
ولأنك مهندس بارع، تصنع موزّع طعام اسمه FEED-M-ALL.
آخر ما تحتاجه لإتمام مشروعك هو قطعة كود تحسب كمية العلف التي يجب أن تحصل عليها كل بقرة. من المهم أن تحصل كل بقرة على الكمية نفسها، فعليك تجنّب النزاعات. الأبقار حساسة للغاية.
لحسن الحظ، لست بحاجة إلى استنتاج كل صيغ حساب كميات العلف بنفسك. أنت تستخدم مكتبة خارجية غامضة وجدتها على الإنترنت. من المفترض أن تؤدي إلى أسعد الأبقار. تعرض المكتبة صنفًا يحقق الواجهة التالية. ستعتمد عليه في الكود الذي تكتبه بنفسك.
class FodderCalculator
def fodder_amount! : Number
end
def fattening_factor! : Number
end
end
بينما تعمل على الكود، ستحسّن معالجة الأخطاء لتجعله أكثر متانة وأسهل في debug لاحقًا عندما تستخدمه في حياتك اليومية في المزرعة.
أولًا، تركّز على كتابة الكود اللازم لحساب كمية العلف لكل بقرة.
نفّذ طريقة TheFarm.divide_food تقبل كوسائط كائن FodderCalculator وعددًا صحيحًا للأبقار.
في هذه المهمة، افترض أن عدد الأبقار المُمرَّر أكبر دائمًا من صفر.
يجب أن تُرجع الطريقة كمية الطعام لكل بقرة.
لإجراء الحساب، تحتاج أولًا إلى الحصول على الكمية الإجمالية للعلف لجميع الأبقار.
ويتم ذلك باستدعاء الطريقة fodder_amount! وتمرير عدد الأبقار.
بالإضافة إلى ذلك، تحتاج إلى معامل يجب ضرب هذه الكمية فيه.
تحصل على هذا المعامل باستدعاء الطريقة fattening_factor!.
بهاتين القيمتين وعدد الأبقار، يمكنك الآن حساب كمية الطعام لكل بقرة (بصيغة float64).
إذا أرجعت إحدى الطرق التي تستدعيها خطأً، فيجب أن يتوقف التنفيذ وتُرجع 0.
fodder_calculator = FodderCalculator.new(50, 1.5)
TheFarm.divide_food(fodder_calculator, 5)
# => 15.0
fodder_calculator = FodderCalculator.new(50, nil)
TheFarm.divide_food(fodder_calculator, 5)
# => 0
أثناء عملك على المهمة الأولى أعلاه، أدركت أن المكتبة الخارجية التي تستخدمها ليست بجودة عالية كما كنت تظن. على سبيل المثال، لا تستطيع التعامل بشكل صحيح مع المدخلات غير الصالحة. تريد التحايل على هذا القيد بإضافة فحص لقيمة المدخل في الكود الخاص بك.
اكتب دالة TheFarm.validate_input_and_divide_food لها التوقيع نفسه الذي لـ TheFarm.divide_food أعلاه.
TheFarm.divide_food وتُرجع نتائج ذلك الاستدعاء."Number of cows must be greater than 0".TheFarm.validate_input_and_divide_food(fodderCalculator, 5)
# => 15
TheFarm.validate_input_and_divide_food(fodderCalculator, -2)
# Error: Number of cows must be greater than 0
كان التحقق من عدد الأبقار قبل تمريره خطوة جيدة، لكنك لست راضيًا تمامًا عن رسالة الخطأ غير المحددة.
تقرر أن تحسّن الأمر بإنشاء صنف خطأ مخصص اسمه InvalidCowsError.
يجب أن يحمل الخطأ المخصص عدد الأبقار (Int32) ورسالة مخصصة (String)، وأن تسلسل طريقة Expection البيانات بالتنسيق التالي:
{number of cows} cows are invalid: {custom message}
بعد أن جهّزت خطأك المخصص، نفّذ دالة TheFarm.validate_number_of_cows تقبل عدد الأبقار كعدد صحيح وتُرجع خطأً (أو nil).
InvalidCowsError مع ضبط الرسالة المخصصة على "there are no negative cows".InvalidCowsError مع ضبط الرسالة المخصصة على "no cows don't need food".nil للإشارة إلى نجاح التحقق.TheFarm.validate_number_of_cows(5)
# => nil
TheFarm.validate_number_of_cows(-5)
# => InvalidCowsError: -5 cows are invalid: there are no negative cows
بعد العمل الشاق في إعداد دالة التحقق هذه، تلاحظ أن المساء قد حلّ، فتترك مكتبك لتستمتع بغروب الشمس فوق الجبال. وتؤجل مهمة إضافة دالة التحقق الجديدة فعليًا إلى كودك إلى يوم آخر.
سجّل في Exercism لتتعلّم وتتقن Crystal عبر 26 مفهومًا142 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.