معالجة الاستثناءات (البرمجة)

في برمجة الحاسوب ، توجد آليات متعددة في لغات البرمجة لمعالجة الاستثناءات . يُستخدم مصطلح "الاستثناء" عادةً للدلالة على بنية بيانات تخزن معلومات حول حالة استثنائية. إحدى آليات نقل التحكم، أو إطلاق الاستثناء، تُعرف باسم "الرمي "؛ ويُقال حينها إن الاستثناء قد أُطلق . ثم يُنقل التنفيذ إلى " التقاط" .

الاستخدام

تختلف لغات البرمجة اختلافًا كبيرًا في مفهومها للاستثناء. يمكن استخدام الاستثناءات لتمثيل ومعالجة الحالات الشاذة وغير المتوقعة والخاطئة، وكذلك كبنى للتحكم في تدفق البرنامج للتعامل مع الحالات العادية. على سبيل المثال، تُطلق دوال التكرار في بايثون استثناءات StopIteration للإشارة إلى عدم وجود عناصر أخرى ينتجها المُكرِّر. [ 1 ] يوجد اختلاف في الآراء بين العديد من اللغات حول الاستخدام الأمثل للاستثناءات. على سبيل المثال، يذكر جوشوا بلوخ أن استثناءات جافا يجب استخدامها فقط في الحالات الاستثنائية، [ 2 ] لكن كينيري يلاحظ أن فئة جافا java.io.FileNotFoundExceptionليست حدثًا استثنائيًا على الإطلاق. [ 3 ] وبالمثل، يذكر بيارن ستروستروب، مؤلف لغة C++، أن استثناءات C++ يجب استخدامها فقط لمعالجة الأخطاء، لأن هذا هو الغرض الذي صُممت من أجله، [ 4 ] لكن كينيري يلاحظ أن العديد من اللغات الحديثة مثل Ada وC++ وModula-3 وML وOCaml وبايثون وروبي تستخدم الاستثناءات للتحكم في تدفق البرنامج. بذلت بعض اللغات مثل إيفل، وسي شارب، وكومون ليسب، وموديولا-2 جهودًا حثيثة للحد من استخدامها للاستثناءات، على الرغم من أن ذلك يتم على مستوى اجتماعي وليس تقني. [ 3 ]

تاريخ

كانت مُجمِّعات لغة فورتران الأولى من IBM تحتوي على عبارات لاختبار الحالات الاستثنائية، بما في ذلك عبارات ` require` و`require` و` require`. ولضمان استقلالية اللغة عن الجهاز، لم تُضمَّن هذه العبارات في فورتران 4 ولا في معيار فورتران 66. مع ذلك، منذ إصدار فورتران 2003، أصبح من الممكن اختبار المشكلات العددية عبر استدعاء الدوال في الوحدة النمطية.IF ACCUMULATOR OVERFLOWIF QUOTIENT OVERFLOWIF DIVIDE CHECKIEEE_EXCEPTIONS

استمر تطوير معالجة استثناءات البرمجيات في الستينيات والسبعينيات. سمحت لغة LISP 1.5 (1958-1961) [ 5 ] بإثارة الاستثناءات بواسطة ERRORالدالة الزائفة، على غرار الأخطاء التي يثيرها المفسر أو المترجم. وكانت الاستثناءات تُلتقط بواسطة ERRORSETالكلمة المفتاحية، التي تُعيد قيمة NILفي حالة حدوث خطأ، بدلاً من إنهاء البرنامج أو الدخول إلى مصحح الأخطاء . [ 6 ] وقدّمت لغة PL/I شكلها الخاص من معالجة الاستثناءات حوالي عام 1964، مما سمح بمعالجة المقاطعات باستخدام وحدات ON. [ 7 ] ولاحظت لغة MacLispERRSET أن و ERRلم تُستخدم فقط لإثارة الأخطاء، ولكن أيضًا للتحكم في تدفق البرنامج غير المحلي، وبالتالي أضافت كلمتين مفتاحيتين جديدتين، CATCHو THROW(يونيو 1972). [ 8 ] وتم تقديم سلوك التنظيف الذي يُطلق عليه الآن عمومًا اسم "finally" في لغة NIL (التنفيذ الجديد للغة LISP) في منتصف إلى أواخر السبعينيات UNWIND-PROTECT. [ ٩ ] ثم تبنت لغة Common Lisp هذا النهج . وتزامن ذلك مع dynamic-windلغة Scheme التي تعاملت مع الاستثناءات في الدوال المغلقة. وكانت أولى الأبحاث حول معالجة الاستثناءات المنظمة هي بحثا Goodenough (١٩٧٥أ) و Goodenough (١٩٧٥ب) . [ ١٠ ] ومنذ ثمانينيات القرن العشرين، اعتمدت العديد من لغات البرمجة معالجة الاستثناءات على نطاق واسع.

بناء الجملة

تتضمن العديد من لغات البرمجة دعمًا نحويًا مدمجًا للاستثناءات ومعالجتها. ويشمل ذلك لغات مثل Ada و BlitzMax و C++ و C# و Clojure و COBOL و D و ECMAScript (مثل ActionScript و JavaScript ) و Eiffel و Java و ML و Object Pascal (مثل Delphi و Free Pascal ) و PowerBuilder و Objective-C و OCaml و Perl و PHP (ابتداءً من الإصدار 5) و PL / I و PL /SQL و Prolog و Python و REALbasic و Ruby و Scala و Smalltalk و Tcl و Visual Prolog ومعظم لغات .NET .

باستثناء بعض الاختلافات النحوية الطفيفة، لا تُستخدم سوى بضعة أنماط لمعالجة الاستثناءات. في النمط الأكثر شيوعًا، يُنشأ الاستثناء بواسطة عبارة خاصة ( throwأو raise) مع كائن استثناء (كما في Java أو Object Pascal) أو قيمة من نوع تعداد قابل للتوسيع (كما في Ada أو SML). يبدأ نطاق معالجات الاستثناءات بعبارة علامة ( tryأو بداية كتلة اللغة مثل begin) وينتهي ببداية أول عبارة معالجة ( catch, except, rescue). يمكن أن تتبعها عدة عبارات معالجة، ويمكن لكل منها تحديد أنواع الاستثناءات التي تعالجها والاسم الذي تستخدمه لكائن الاستثناء. وكاختلاف طفيف، تستخدم بعض اللغات عبارة معالجة واحدة، تتعامل مع فئة الاستثناء داخليًا.

من الشائع أيضًا استخدام عبارة ذات صلة ( finallyأو ensure) تُنفذ سواء حدث استثناء أم لا، وعادةً ما يكون ذلك لتحرير الموارد التي تم الحصول عليها داخل كتلة معالجة الاستثناء. والجدير بالذكر أن لغة C++ لا توفر هذا التركيب، بل توصي بتقنية RAII ( الاستحواذ على الموارد هو التهيئة ) التي تُحرر الموارد باستخدام المُدمرات . [ 12 ] وفقًا لورقة بحثية نُشرت عام 2008 من قِبل ويستلي وايمر وجورج نيكولا ، فإن صيغة كتل try... finallyفي جافا تُعد عاملًا مساهمًا في عيوب البرمجيات. فعندما تحتاج دالة ما إلى معالجة الحصول على 3-5 موارد وتحريرها، يبدو أن المبرمجين يترددون في تضمين عدد كافٍ من الكتل بسبب مخاوف تتعلق بسهولة القراءة، حتى وإن كان هذا حلًا صحيحًا. من الممكن استخدام كتلة try... واحدة finallyحتى عند التعامل مع موارد متعددة، ولكن ذلك يتطلب استخدامًا صحيحًا لقيم الحارس ، وهو مصدر شائع آخر للأخطاء في هذا النوع من المشاكل. [ 13 ] : 8:6-8:7

تسمح لغتا بايثون وروبي أيضًا بوجود عبارة ( else) يتم استخدامها في حالة عدم حدوث أي استثناء قبل الوصول إلى نهاية نطاق المعالج.

قد يبدو رمز معالجة الاستثناءات بشكل عام كما يلي ( بصيغة معالجة الاستثناءات على نمط جافا ):

استيراد java.io.IOException ؛ استيراد java.util.Scanner ؛حاول { Scanner stdin = new Scanner ( System . in ); String line = stdin . nextLine ();إذا كان طول السطر يساوي صفرًا ، فسيتم طرح استثناء IOException مع رسالة " السطر المقروء من وحدة التحكم كان فارغًا ! " .System.out.printf ( " Hello % s ! %n" , line ); System.out.println ( " تم تنفيذ المهمة بنجاح." ) ; } catch ( IOException e ) { System.out.println ( " Hello !" ) ; } catch ( Exception e ) { System.out.printf ( " Error : %s%n " , e.getMessage ( ) ) ; } finally { System.out.println ( " سيتم إنهاء البرنامج الآن . " ) ; }

لا تحتوي لغة C على معالجة استثناءات try-catch، ولكنها تستخدم رموز الإرجاع للتحقق من الأخطاء. ويمكن استخدام دوال المكتبة القياسية لتنفيذ معالجة try-catch عبر وحدات الماكرو setjmp. longjmp[ 14 ]

تستخدم لغة بيرل 5 حلقة diefor throwوحلقات try-catch. وتحتوي على وحدات CPAN التي توفر دلالات try-catch. [ 15 ]eval{}if($@){}

دلالات الإنهاء والاستئناف

عند حدوث استثناء، يبحث البرنامج في مكدس استدعاءات الدوال حتى يعثر على معالج الاستثناء. تتطلب بعض لغات البرمجة تفريغ المكدس أثناء تقدم هذا البحث. بمعنى آخر، إذا استدعت الدالة f، التي تحتوي على معالج Hللاستثناء E، الدالة g، والتي بدورها استدعت الدالة h، وحدث استثناء Eفي ، فقد يتم إنهاء hالدالتين hو ، وستتولى الدالة معالجة . يُعرف هذا بدلالات الإنهاء. بدلاً من ذلك، قد لا تقوم آليات معالجة الاستثناءات بتفريغ المكدس عند الوصول إلى معالج الاستثناء [ ملاحظة 1 ] ، مما يمنح معالج الاستثناء خيار إعادة بدء الحساب أو استئنافه أو تفريغه. يسمح هذا للبرنامج بمواصلة الحساب من نفس المكان الذي حدث فيه الخطأ (على سبيل المثال، عند توفر ملف كان مفقودًا سابقًا) أو لتنفيذ الإشعارات والتسجيل والاستعلامات والمتغيرات المرنة فوق آلية معالجة الاستثناءات (كما هو الحال في لغة Smalltalk). يُطلق على السماح للحساب باستئناف من حيث توقف اسم دلالات الاستئناف.gHfE

توجد حجج نظرية وتصميمية تدعم كلا القرارين. وقد أسفرت مناقشات توحيد معايير لغة C++ في الفترة 1989-1991 عن قرار نهائي باستخدام دلالات الإنهاء في لغة C++. [ 16 ] ويستشهد بيارن ستروستروب بعرض تقديمي لجيم ميتشل كنقطة بيانات رئيسية:

استخدم جيم معالجة الاستثناءات في ست لغات برمجة على مدى عشرين عامًا، وكان من أوائل المؤيدين لدلالات الاستئناف، كونه أحد المصممين والمنفذين الرئيسيين لنظام Cedar/Mesa من شركة زيروكس . وكانت رسالته هي

"يُفضّل إنهاء العمل على استئنافه؛ وهذا ليس رأياً شخصياً، بل هو نتيجة سنوات من الخبرة. الاستئناف مغرٍ، ولكنه غير صحيح."

دعم هذا التصريح بتجاربه مع أنظمة تشغيل متعددة. وكان المثال الأبرز هو نظام Cedar/Mesa: فقد صُمم هذا النظام من قِبل أشخاصٍ كانوا يُفضلون استخدام خاصية استئناف التنفيذ، ولكن بعد عشر سنوات من الاستخدام، لم يتبقَّ سوى استخدام واحد لهذه الخاصية في النظام الذي يبلغ نصف مليون سطر برمجي، وهو الاستعلام عن السياق. ولأن خاصية استئناف التنفيذ لم تكن ضرورية في الواقع لمثل هذا الاستعلام، فقد تم حذفها، ولاحظوا زيادة ملحوظة في سرعة ذلك الجزء من النظام. في كل حالة استُخدمت فيها خاصية استئناف التنفيذ، أصبحت - على مدار السنوات العشر - مشكلة، وتم استبدالها بتصميم أكثر ملاءمة. باختصار، كان كل استخدام لخاصية استئناف التنفيذ يُمثل فشلاً في الحفاظ على مستويات التجريد منفصلة. [ 10 ]

تشمل لغات معالجة الاستثناءات مع خاصية الاستئناف لغة Common Lisp بنظامها الشرطي ، وPL/I، وDylan، وR ، [ 17 ] و Smalltalk . مع ذلك، فإن غالبية لغات البرمجة الحديثة تتبع لغة C++ وتستخدم دلالات الإنهاء.

في لغة C++، std::uncaught_exceptions()تُستخدم دالة لحساب عدد الاستثناءات في الخيط الحالي التي تم طرحها/إعادة طرحها ولم تدخل بعد في catchكتلة مطابقة. قبل C++20 ، كانت تُستخدم دالة أخرى std::uncaught_exception()لتحديد ما إذا كان فكّ تكديس الذاكرة يحدث أم لا. [ 18 ]

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

يتطلب تطبيق معالجة الاستثناءات في لغات البرمجة عادةً قدرًا كبيرًا من الدعم من كلٍّ من مولد الشفرة ونظام التشغيل المصاحب للمترجم. (كانت إضافة معالجة الاستثناءات إلى لغة C++ هي التي أنهت العمر الافتراضي لمترجم C++ الأصلي، Cfront . [ 19 ] ) هناك طريقتان شائعتان. الأولى،تُنشئ عملية التسجيل الديناميكي شيفرةً تُحدِّث باستمرار هياكل بيانات حالة البرنامج فيما يتعلق بمعالجة الاستثناءات. [ 20 ] عادةً، يُضيف هذا عنصرًا جديدًا إلىتخطيط إطار المكدس، يعرف معالجات الاستثناءات المتاحة للدالة أو الطريقة المرتبطة بهذا الإطار؛ فإذا حدث استثناء، يُشير مؤشر في التخطيط إلى شيفرة المعالج المناسبة. يتميز هذا الأسلوب بصغر حجمه، ولكنه يُضيف عبئًا إضافيًا على التنفيذ عند دخول الإطار والخروج منه. وقد شاع استخدامه في العديد من تطبيقات لغة آدا، على سبيل المثال، حيث كانت هناك حاجة مُسبقة إلى توليد معقد ودعم وقت التشغيل للعديد من ميزات اللغة الأخرى.نظام معالجة الاستثناءات المهيكلة(SEH) من مايكروسوفت (32 بت) هذا الأسلوب مع مكدس استثناءات منفصل. [ 21 ] ونظرًا لسهولة تعريف التسجيل الديناميكي، فإنه قابل للتحققمن صحته. [ 22 ]

أما المخطط الثاني، وهو المخطط المُطبق في العديد من مُجمّعات لغة C++ عالية الجودة وفي نظام معالجة الاستدلال المتزامن (SEH) من مايكروسوفت ذي 64 بت ، فهوالنهج القائم على الجداول . يُنشئ هذا النهج جداول ثابتة أثناءالترجمةوالربط،تربط نطاقاتعداد البرنامجبحالة البرنامج فيما يتعلق بمعالجة الاستثناءات. [ 23 ] بعد ذلك، في حال حدوث استثناء، يبحث نظام التشغيل عن موقع التعليمات الحالية في الجداول، ويحدد معالجات الاستثناءات المُفعّلة والإجراءات المطلوبة. يُقلل هذا النهج من الحمل الزائد على وحدة المعالجة التنفيذية في حالة عدم حدوث استثناء. يحدث هذا على حساب مساحة تخزين، ولكن يمكن تخصيص هذه المساحة لأقسام بيانات خاصة للقراءة فقط، لا يتم تحميلها أو نقلها إلا عند حدوث استثناء فعلي. [ 24 ] لا يشترط أن يكون موقع كود معالجة الاستثناء (في الذاكرة) داخل (أو حتى بالقرب من) منطقة الذاكرة التي يُخزن فيها باقي كود الدالة. لذا، في حال حدوث استثناء، قد يحدث انخفاض في الأداء - يُقارب تأثير استدعاء دالة [ 25 ] - إذا تطلب الأمر تحميل/تخزين كود معالجة الاستثناءات اللازم. مع ذلك، يكون لهذا المخطط تأثير ضئيل على الأداء في حال عدم حدوث أي استثناء. بما أن الاستثناءات في لغة C++ تُعتبراستثنائيةيُستخدممصطلح "الاستثناءات عديمة التكلفة" [ ملاحظة 2 ] أحيانًا لوصف معالجة الاستثناءات في C++. ومثل تحديد نوع وقت التشغيلبمبدأ التكلفة الصفريةفي C++، إذ تتطلب معالجة الاستثناءات في وقت التشغيل مقدارًا غير صفري من الذاكرة لجدولالبحث. [ 26 ] لهذا السبب، يمكن تعطيل معالجة الاستثناءات (وRTTI) في العديد من مُجمِّعات C++، وهو ما قد يكون مفيدًا للأنظمة ذات الذاكرة المحدودة جدًا [ 26 ] (مثلالأنظمة المُدمجة). كما أن هذا النهج الثاني أفضل من حيث تحقيقسلامة الخيوط.

بالمقارنة مع لغة C++ حيث يمكن طرح أي نوع من الأخطاء والتقاطه، في لغة Java java.lang.Throwableلا يمكن طرح سوى الأنواع التي ترث من نوع معين والتقاطها، java.lang.Throwableولها فئتان فرعيتان مباشرتان: java.lang.Error(تشير إلى مشكلة خطيرة لا يحتاج البرنامج العادي إلى التقاطها)، و java.lang.Exception(أي حالة أخرى قد يرغب البرنامج العادي في التقاطها ومعالجتها). java.lang.Errorعادةً ما تُستخدم هذه الفئة للمشاكل الخطيرة للغاية التي تتجاوز نطاق البرنامج، مثل java.lang.OutOfMemoryErrorالأخطاء java.lang.ThreadDeathأو java.lang.AssertionError.

كما تم اقتراح مخططات تعريفية وتنفيذية أخرى. بالنسبة للغات التي تدعم البرمجة الوصفية ، تم تطوير مناهج لا تتضمن أي تكلفة إضافية على الإطلاق (باستثناء الدعم الموجود بالفعل للانعكاس ) . [ 27 ]

معالجة الاستثناءات بناءً على التصميم التعاقدي

ثمة منظور مختلف للاستثناءات يستند إلى مبادئ التصميم التعاقدي ، ويدعمه بشكل خاص لغة إيفل . وتتمثل الفكرة في توفير أساس أكثر دقة للتعامل مع الاستثناءات من خلال تحديد السلوك "الطبيعي" و"غير الطبيعي" بدقة. ويعتمد هذا النهج تحديدًا على مفهومين:

  • الفشل : هو عدم قدرة عملية ما على الوفاء بمتطلباتها. على سبيل المثال، قد ينتج عن عملية جمع خطأ في الحساب (أي أنها لا تفي بمتطلباتها المتمثلة في حساب قيمة تقريبية جيدة للمجموع الرياضي)؛ أو قد يفشل روتين ما في تلبية شرطه اللاحق.
  • الاستثناء : حدث غير طبيعي يقع أثناء تنفيذ روتين معين (هذا الروتين هو " المتلقي " للاستثناء). وينتج هذا الحدث غير الطبيعي عن فشل عملية استدعاها الروتين.

ينص "مبدأ معالجة الاستثناءات الآمنة" كما قدمه برتراند ماير في كتاب "بناء البرمجيات الموجهة للكائنات" على أنه لا توجد سوى طريقتين ذوات معنى يمكن أن يتفاعل بهما الروتين عند حدوث استثناء:

  • الفشل، أو "الذعر المنظم": يقوم الروتين بإصلاح حالة الكائن عن طريق إعادة تأسيس الثابت (هذا هو الجزء "المنظم")، ثم يفشل (يصاب بالذعر)، مما يؤدي إلى حدوث استثناء في المتصل به (بحيث لا يتم تجاهل الحدث غير الطبيعي).
  • إعادة المحاولة: يحاول الروتين تطبيق الخوارزمية مرة أخرى، عادةً بعد تغيير بعض القيم بحيث تكون للمحاولة التالية فرصة أفضل للنجاح.

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

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

إرسال ( م : رسالة ) هو -- إرسال الرسالة عبر رابط سريع، إن أمكن، وإلا عبر رابط بطيء. محلي تم تجربته سريعًا ، تم تجربته بطيئًا : منطقي do إذا تم تجربته سريعًا، فإن تم تجربته بطيئًا := صحيح أرسل_بطيئًا ( م ) وإلا تم تجربته سريعًا := صحيح أرسل_سريعًا ( م ) نهاية الإنقاذ إذا لم يتم تجربته بطيئًا، فأعد المحاولة نهاية نهاية

تُهيأ المتغيرات المحلية المنطقية إلى القيمة False في البداية. إذا فشلت العملية، فسيتم تنفيذ send_fastالجزء ( البند) مرة أخرى، مما يؤدي إلى تنفيذ . إذا فشل تنفيذ هذا الجزء ، فسيتم تنفيذ البند حتى النهاية بدون (لا يوجد بند في النهاية )، مما يؤدي إلى فشل تنفيذ الروتين ككل.dosend_slowsend_slowrescueretryelseif

يتميز هذا النهج بتحديد واضح للحالات "العادية" و"غير العادية": فالحالة غير العادية، التي تُسبب استثناءً، هي الحالة التي يعجز فيها الروتين عن الوفاء بمتطلباته. كما يُحدد توزيعًا واضحًا للأدوار: فالبند do(الجزء العادي) مسؤول عن تحقيق متطلبات الروتين، أو محاولة تحقيقها؛ والبند rescueمسؤول عن إعادة تهيئة السياق وإعادة تشغيل العملية، إذا كان ذلك مُمكنًا، ولكنه غير مسؤول عن إجراء أي عملية حسابية فعلية.

على الرغم من أن الاستثناءات في لغة إيفل تتمتع بفلسفة واضحة إلى حد ما، إلا أن كينيري (2006) ينتقد تطبيقها لأن "الاستثناءات التي تُعد جزءًا من تعريف اللغة تُمثل بقيم عددية صحيحة، بينما تُمثل الاستثناءات التي يُحددها المطور بقيم نصية. [...] بالإضافة إلى ذلك، ولأنها قيم أساسية وليست كائنات، فإنها لا تحمل دلالات جوهرية تتجاوز ما هو مُعبر عنه في روتين مساعد، وهو ما لا يمكن أن يكون مضمونًا تمامًا بسبب التحميل الزائد للتمثيل (على سبيل المثال، لا يمكن التمييز بين عددين صحيحين لهما نفس القيمة)." [ 3 ]

يُضيف C++26 دعمًا للعقود، والتي تُستخدم على النحو التالي. [ 28 ]

int f ( const int x ) pre ( x != 1 ) // تأكيد شرط مسبق post ( r : r == x && r != 2 ) // تأكيد شرط لاحق؛ r هو اسم كائن نتيجة f { contract_assert ( x != 3 ); // عبارة تأكيد return x ; }

استثناءات غير معالجة

تواجه التطبيقات المعاصرة العديد من تحديات التصميم عند النظر في استراتيجيات معالجة الاستثناءات. لا سيما في تطبيقات المؤسسات الحديثة، غالبًا ما تتجاوز الاستثناءات حدود العمليات وحدود الأجهزة. جزء من تصميم استراتيجية فعّالة لمعالجة الاستثناءات هو تحديد متى تفشل عملية ما إلى الحد الذي لا يمكن فيه معالجتها اقتصاديًا بواسطة الجزء البرمجي من العملية. [ 29 ]

إذا تم طرح استثناء ولم يتم التقاطه (عمليًا، يتم طرح استثناء عندما لا يتم تحديد معالج مناسب)، فإن وقت التشغيل يتعامل مع الاستثناء غير الملتقط؛ ويسمى الروتين الذي يقوم بذلك بـمعالج الاستثناءات غير المعالجة . [ 30 ] [ 31 ] السلوك الافتراضي الأكثر شيوعًا هو إنهاء البرنامج وطباعةرسالة خطأعلى وحدة التحكم، تتضمن عادةً معلومات تصحيح الأخطاء مثل تمثيل نصي للاستثناءوتتبع المكدس. [ 30 ] [ 32 ] [ 33 ] غالبًا ما يتم تجنب ذلك من خلال وجود معالج على مستوى التطبيق (على سبيل المثال فيحلقة أحداث) يقوم بالتقاط الاستثناءات قبل وصولها إلى وقت التشغيل. [ 30 ] [ 34 ]

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

في البرامج متعددة الخيوط، قد يؤدي استثناء غير معالج في أحد الخيوط إلى إنهاء ذلك الخيط فقط، وليس العملية بأكملها (حيث يتم التقاط الاستثناءات غير المعالجة في معالج مستوى الخيط بواسطة معالج المستوى الأعلى). وهذا مهم بشكل خاص للخوادم، حيث يمكن، على سبيل المثال، إنهاء سيرفلت (يعمل في خيط منفصل) دون أن يتأثر الخادم ككل.

يمكن تجاوز معالج الاستثناءات غير المعالجة الافتراضي هذا، إما على مستوى النظام أو لكل سلسلة عمليات، على سبيل المثال لتوفير تسجيل بديل أو إبلاغ المستخدم النهائي عن الاستثناءات غير المعالجة، أو لإعادة تشغيل سلاسل العمليات التي تنتهي بسبب استثناء غير معالج. على سبيل المثال، في جافا، يتم ذلك لسلسلة عمليات واحدة عبر ` Thread.setUncaughtExceptionHandlerfor` وعلى مستوى النظام عبر `for` Thread.setDefaultUncaughtExceptionHandler؛ وفي بايثون، يتم ذلك عن طريق تعديل `for` sys.excepthook.

الاستثناءات التي تم التحقق منها

قدمت لغة جافا مفهوم الاستثناءات المُدققة، [ 35 ] [ 36 ] وهي فئات خاصة من الاستثناءات. في جافا، الاستثناء المُدقق هو أي استثناء java.lang.Throwableلا يمتد من `<T>` java.lang.RuntimeExceptionأو `<T> java.lang.Error`. يجب أن تكون الاستثناءات المُدققة التي قد يثيرها أي دالة جزءًا من توقيعها . على سبيل المثال، إذا كان من الممكن أن تُثير دالة استثناءً من نوع `<T>` java.io.IOException، فيجب عليها التصريح بذلك صراحةً في توقيعها. يؤدي عدم القيام بذلك إلى ظهور خطأ أثناء الترجمة. يتم التصريح بذلك على النحو التالي (مع `<T>` أيضًا java.util.zip.DataFormatException):

استيراد java.io.File ؛ استيراد java.io.IOException ؛ استيراد java.util.zip.DataFormatException ؛// يشير إلى إمكانية حدوث استثناءات IOException و DataFormatException public void operantOnFile ( File f ) throws IOException , DataFormatException { // ... }

بحسب هانزبيتر موسنبوك، فإن الاستثناءات المُدققة أقل ملاءمة ولكنها أكثر متانة. [ 37 ] يمكن للاستثناءات المُدققة، أثناء وقت الترجمة ، أن تقلل من حدوث الاستثناءات غير المعالجة التي تظهر أثناء وقت التشغيل في تطبيق معين.

يكتب كينيري: "كما يعلم أي مبرمج جافا، فإن حجم try catchالكود في تطبيق جافا نموذجي يكون أحيانًا أكبر من حجم الكود اللازم للتحقق الصريح من المعاملات الرسمية وقيم الإرجاع في لغات أخرى لا تدعم الاستثناءات المُدققة. في الواقع، يتفق معظم مبرمجي جافا على أن التعامل مع الاستثناءات المُدققة مهمة شاقة تقريبًا مثل كتابة التوثيق. ولذلك، يُعرب العديد من المبرمجين عن استيائهم من هذه الاستثناءات." [ 3 ] كتب مارتن فاولر : "...بشكل عام، أعتقد أن الاستثناءات مفيدة، لكن الاستثناءات المُدققة في جافا تُسبب مشاكل أكثر من فوائدها." [ 38 ] حتى عام 2006، لم تحذُ أي لغة برمجة رئيسية حذو جافا في إضافة الاستثناءات المُدققة. [ 38 ] على سبيل المثال، لا تتطلب لغة C# أو تسمح بتعريف أي مواصفات للاستثناءات، كما هو موضح في ما نشره إريك غونرسون: [ 39 ] [ 3 ] [ 38 ]

"يؤدي فحص البرامج الصغيرة إلى استنتاج مفاده أن اشتراط مواصفات الاستثناءات يمكن أن يعزز إنتاجية المطورين ويحسن جودة التعليمات البرمجية، لكن التجربة مع مشاريع البرمجيات الكبيرة تشير إلى نتيجة مختلفة - انخفاض الإنتاجية وزيادة طفيفة أو معدومة في جودة التعليمات البرمجية."

يصف أندرس هيلسبرغ مشكلتين تتعلقان بالاستثناءات التي تم التحقق منها: [ 40 ]

  • التحكم في الإصدارات: يمكن تعريف دالة بحيث تُطلق استثناءات X. Yفي إصدار لاحق من الكود، لا يمكن إطلاق استثناء Zمن الدالة، لأن ذلك سيجعل الكود الجديد غير متوافق مع الإصدارات السابقة. تتطلب الاستثناءات المُدققة من مُستدعي الدالة إما إضافة استثناء Zإلى عبارة throws الخاصة بهم أو معالجة الاستثناء. بدلاً من ذلك، Zقد يُساء فهم الاستثناء على أنه استثناء Xأو خطأ Y.
  • قابلية التوسع: في التصميم الهرمي، قد يحتوي كل نظام على عدة أنظمة فرعية. وقد يُصدر كل نظام فرعي عدة استثناءات. يجب على كل نظام رئيسي التعامل مع استثناءات جميع الأنظمة الفرعية التابعة له، مما ينتج عنه عدد هائل من الاستثناءات التي يجب معالجتها. تتطلب الاستثناءات المُدققة معالجة جميع هذه الاستثناءات بشكل صريح.

للتغلب على هذه المشكلات، يقول هيلسبرغ إن المبرمجين يلجؤون إلى التحايل على هذه الخاصية باستخدام تعريف. ومن التحايلات الأخرى استخدام معالج استثناءات (أو حتى معالج استثناءات ). [ 40 ] يُشار إلى هذا باسم معالجة الاستثناءات الشاملة أو معالجة استثناءات بوكيمون نسبةً إلى عبارة المسلسل الشهيرة " Gotta Catch 'Em All! ". [ 41 ] لا تشجع دروس جافا التعليمية على استخدام معالجة الاستثناءات الشاملة لأنها قد تلتقط استثناءات "لم يُصمم المعالج من أجلها". [ 42 ] ومن التحايلات الأخرى غير المُشجعة جعل جميع الاستثناءات فئة فرعية ، [ 43 ] مما يجعل الاستثناء غير مُدقق. الحل المُشجع هو استخدام معالج استثناءات شامل أو عبارة throws ولكن مع فئة أساسية مُحددة لجميع الاستثناءات التي يُحتمل طرحها بدلاً من الفئة الأساسية العامة . حل مُشجع آخر هو تعريف وتصريح أنواع استثناءات مناسبة لمستوى تجريد الطريقة المُستدعاة [ 44 ] وربط استثناءات المستوى الأدنى بهذه الأنواع باستخدام تسلسل الاستثناءات .throwsExceptiontry{...}catch(Exceptione){...}try{...}catch(Throwablet){...}java.lang.RuntimeExceptionjava.lang.Throwable

تفتقر لغة Kotlin إلى ميزة التحقق من الاستثناءات، وحتى عند طرح استثناء Java مُتحقق منه من Kotlin، لن يُجبر ذلك العميل على معالجته من Java. مع ذلك، يُمكن تفعيل هذه الميزة باستخدام التعليق @Throwsالتوضيحي، الذي يُرسل throwsالبيانات الوصفية إلى JVM. على سبيل المثال، انظر إلى كود Kotlin التالي:

استيراد java.io.IOException@Throws ( IOException :: class ) fun readFile () { // ... throw IOException ( "فشل في قراءة الملف!" ) }

وهذا يُترجم تقريبًا إلى ما يلي في JVM:

استيراد java.io.IOException ؛public static final void readFile () throws IOException { // ... throw new IOException ( "فشل في قراءة الملف!" ); }

آليات مماثلة

تعود جذور الاستثناءات المُدققة إلى مفهوم تحديد الاستثناءات في لغة برمجة CLU . [ 45 ] إذ لا يمكن للدالة أن تُثير إلا الاستثناءات المُدرجة في نوعها، ولكن أي استثناءات مُسرّبة من الدوال المُستدعاة تُحوّل تلقائيًا إلى استثناء وقت التشغيل الوحيد، failureبدلًا من أن تُؤدي إلى خطأ وقت الترجمة. [ 46 ] لاحقًا، احتوت لغة Modula-3 على ميزة مُشابهة. [ 47 ] لا تتضمن هذه الميزات التحقق في وقت الترجمة الذي يُعدّ جوهريًا في مفهوم الاستثناءات المُدققة. [ 45 ]

تضمنت الإصدارات الأولى من لغة البرمجة C++ آلية اختيارية مشابهة للاستثناءات المُدققة، تُسمى مواصفات الاستثناءات . افتراضيًا، يمكن لأي دالة أن تُطلق أي استثناء، ولكن يمكن تقييد ذلك بشرط throw(مشابه للشرط في Java) يُضاف إلى توقيع الدالة، يُحدد الاستثناءات التي يُمكن للدالة إطلاقها. على سبيل المثال، كان هذا الكود صالحًا في C++03 :throws

#include <stdexcept>باستخدام std :: domain_error ؛ باستخدام std :: invalid_argument ؛// قد يكون هذا مشابهًا لتوقيع جافا // void performSomeOperation(int a, int b) throws InvalidArgumentException, ArithmeticException; void performSomeOperation ( int a , int b ) throw ( invalid_argument , domain_error ) { // ... }

يمكن لعبارات C++ throwتحديد أي عدد من أي نوع، حتى الأنواع الأولية والفئات التي لا ترث من فئات أخرى std::exception(لأن C++ تدعم إطلاق استثناءات من أي نوع). إذا لم يُحدد أي نوع في throwالعبارة، فهذا يعني أن الدالة لن تُطلق أي استثناءات على الإطلاق.

لم تُفرض مواصفات الاستثناءات أثناء الترجمة. وكانت المخالفات تؤدي إلى استدعاء دالة المكتبة القياسية std::unexpected()[ ملاحظة 3 ] . [ 48 ] كان من الممكن إعطاء مواصفات استثناء فارغة، مما يشير إلى أن الدالة لن تُطلق أي استثناء. لم يُعتمد هذا كإعداد افتراضي عند إضافة معالجة الاستثناءات إلى اللغة لأنه كان سيتطلب تعديلات كبيرة على الشيفرة البرمجية الموجودة، وكان سيعيق التفاعل مع الشيفرة المكتوبة بلغات أخرى، وكان سيُغري المبرمجين بكتابة عدد كبير جدًا من معالجات الاستثناءات على المستوى المحلي. [ 48 ] مع ذلك، فإن الاستخدام الصريح لمواصفات الاستثناءات الفارغة قد يسمح لمترجمات C++ بإجراء تحسينات كبيرة على الشيفرة البرمجية وتخطيط المكدس، وهي تحسينات غير ممكنة عند معالجة الاستثناءات داخل دالة. [ 24 ] رأى بعض المحللين أن الاستخدام الأمثل لمواصفات الاستثناءات في C++ أمرٌ صعب التحقيق. [ 49 ] تم تضمين استخدام مواصفات الاستثناءات هذا في C++98 و C++03 ، وتم إهماله في معيار لغة C++ لعام 2012 ( C++11[ 50 ] وأُزيل من اللغة في C++17 . استُبدلت عبارات throws noexceptبعبارات أخرى. يُشار الآن إلى الدالة التي لن تُطلق أي استثناءات بالكلمة noexceptالمفتاحية، بينما تُحدد الكلمة المفتاحية أن الدالة ستُطلق استثناءً. على الرغم من إزالة عبارات throws من اللغة، فإن كتابة عبارات throws فقط في التوقيع مسموح به، وهو ما يُعادل (عدم تحديد استثناء بواسطة عبارة throws يُشير إلى أنها لا تستطيع إطلاق استثناء)، ومع ذلك، لا يزال هذا يُعتبر مُهملًا. لنقل قاعدة بيانات برمجية تستخدم عبارات throws، يمكن إعادة تعريف الكلمة المفتاحية المُزالة كـ macro. هذا مجرد حل مؤقت للسماح بتجميع الكود، وليس تطبيقًا فعليًا للاستثناءات المُدققة. باستخدام هذا، يمكن محاكاة عبارات Java بشكل مماثل.noexcept(false)throwthrow()noexceptthrowthrowthrowthrows

// إذا كانت الدالة throw() فارغة، فإنها تتوسع إلى noexcept(true) (كما في noexcept)، // وإلا فإنها تتوسع إلى noexcept(false). // لن تتوسع عبارة throw بدون أقواس #define throw(...) noexcept(__VA_OPT__(!)true)استيراد std ؛باستخدام std :: runtime_error ؛class XException : public runtime_error {}; class YException : public runtime_error {};// سيتم توسيع throw(Es...) إلى noexcept(false) void performSomeOperation ( int x , int y ) throw ( XException , YException ) { if ( x > y ) { // لن يتم توسيع throw بدون قوس كـ macro throw XException ( "x > y" ); } else if ( y > x ) { throw YException ( "y > x" ); } std :: println ( "x = y = {}" , x ); }// سيتم توسيع throw() إلى noexcept(true) void willNotThrow ( int x ) throw () { std :: println ( "x = {}" , x ); }

يمكن أيضاً تحديد أن دالة ما noexceptمشروطة بوجود دالة أخرى noexcept، كما يلي:

void mightThrow ();// العبارة الأولى noexcept هي عبارة noexcept، والثانية هي عامل noexcept الذي يُقيّم إلى قيمة منطقية void f () noexcept ( noexcept ( mightThrow ()));

على الرغم من أن لغة C++ لا تدعم الاستثناءات المُدققة، إلا أنه يُمكن تمرير الكائن المُلقى عبر مكدس الاستثناءات داخل catchكتلة برمجية، وذلك بكتابة (دون تحديد كائن). يُعيد هذا الأمر إلقاء الكائن الذي تم التقاطه. يسمح هذا بإجراء عمليات داخل الكتلة البرمجية التي التقطت الاستثناء، قبل السماح للكائن بمواصلة التمرير عبر المكدس.throw;catch

يوجد محلل استثناءات غير معالجة للغة البرمجة OCaml . [ 51 ] تُبلغ الأداة عن مجموعة الاستثناءات المُثارة كتوقيع نوع موسع. ولكن، على عكس الاستثناءات المُدققة، لا تتطلب الأداة أي تعليقات نحوية وهي خارجية (أي أنه من الممكن تجميع وتشغيل برنامج دون التحقق من الاستثناءات).

في لغة C++، يُمكن أيضًا تنفيذ ما يُسمى بـ"معالجة استثناءات بوكيمون". وكما هو الحال في لغة جافا، تدعم C++ كتلة `catch` التي تلتقط أي كائن مُلقى. إلا أن عيبها يكمن في عدم تسمية الكائن الملتقط، مما يعني عدم إمكانية الرجوع إليه. والسبب في ذلك هو أنه في لغات مثل جافا، لا يُسمح إلا بإلقاء الاستثناءات من الفئات التي ترث من فئات أخرى، بينما في C++ يُمكن إلقاء الاستثناءات من أي نوع (حتى الأنواع الأولية)، وبالتالي لا توجد طريقة آمنة ومضمونة لتخزين مرجع للكائن الملتقط من كتلة `catch` الشاملة.catch(Throwablet)catch(...)catch(...)java.lang.Throwable

استيراد std ؛باستخدام std :: exception ؛// التقاط الاستثناءات فقط: حاول { // ... } catch ( const exception & e ) { // التقاط الاستثناءات فقط: std :: println ( "تم التقاط استثناء: {}" , e . what ()); } catch (...) { // التقاط جميع الكائنات التي تم طرحها: std :: println ( "تم التقاط خطأ غير معروف" ); }

بدلاً من استخدام الاستثناءات بشكل كامل، تُمثل لغة Rust الاستثناءات القابلة للاسترداد كأنواع نتائج . [ 52 ] [ 53 ] ويُشار إلى ذلك بـ Result<T, E>(أو expected<T, E>في لغة C++). تكمن ميزة أنواع النتائج على الاستثناءات المُدققة في أنه بينما يُجبر كل من أنواع النتائج والاستثناءات المُدققة المستخدمين على معالجة الأخطاء فورًا، يُمكن أيضًا تمثيلها مباشرةً كنوع إرجاع ضمن نظام أنواع اللغة ، على عكس الاستثناءات المُدققة حيث يكون الاستثناء المُحتمل طرحه جزءًا من توقيع الدالة ولكنه ليس جزءًا مباشرًا من نوع الإرجاع الخاص بها.

التحقق الديناميكي من الاستثناءات

يكمن الهدف من إجراءات معالجة الاستثناءات في ضمان قدرة البرنامج على التعامل مع حالات الخطأ. وللتأكد من كفاءة هذه الإجراءات، من الضروري تعريض البرنامج لمجموعة واسعة من المدخلات غير الصالحة أو غير المتوقعة، كما هو الحال عند استخدام حقن الأخطاء البرمجية واختبار الطفرات (المعروف أحيانًا باختبار التشويش ). يُعدّ برنامج البروتوكولات من أصعب أنواع البرامج التي تتطلب كتابة إجراءات معالجة استثناءات، إذ يجب أن يكون تطبيق البروتوكول القوي مُهيأً لاستقبال مدخلات لا تتوافق مع المواصفات ذات الصلة.

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

في بيئات محركات التشغيل مثل Java أو .NET ، توجد أدوات تتصل بمحرك التشغيل، وفي كل مرة يحدث فيها استثناء مهم، تسجل هذه الأدوات معلومات تصحيح الأخطاء الموجودة في الذاكرة وقت حدوث الاستثناء ( قيم مكدس الاستدعاءات وقيم الكومة ). تُسمى هذه الأدوات بأدوات معالجة الاستثناءات الآلية أو أدوات اعتراض الأخطاء، وتوفر معلومات حول السبب الجذري للاستثناءات.

الاستثناءات غير المتزامنة

الاستثناءات غير المتزامنة هي أحداث تُثار بواسطة خيط منفصل أو عملية خارجية، مثل الضغط على Ctrl-C لمقاطعة برنامج، أو استقبال إشارة ، أو إرسال رسالة مُعطِّلة مثل "إيقاف" أو "تعليق" من خيط تنفيذ آخر . [ 54 ] [ 55 ] بينما تحدث الاستثناءات المتزامنة عند عبارة محددة throw، يمكن إثارة الاستثناءات غير المتزامنة في أي وقت. وعليه، لا يمكن للمُصرِّف تحسين معالجة الاستثناءات غير المتزامنة، لأنه لا يستطيع إثبات عدم وجودها. كما يصعب البرمجة بها بشكل صحيح، إذ يجب حظر الاستثناءات غير المتزامنة أثناء عمليات التنظيف لتجنب تسرب الموارد.

تتجنب لغات البرمجة عادةً معالجة الاستثناءات غير المتزامنة أو تقيدها، فعلى سبيل المثال، تمنع لغة C++ إطلاق الاستثناءات من معالجات الإشارات، وقد أوقفت لغة Java استخدام java.lang.ThreadDeathخاصية الخطأ الخاصة بها في الإصدار 20، والتي كانت تُستخدم للسماح لخيط واحد بإيقاف خيط آخر. [ 56 ] ومن الميزات الأخرى آلية شبه متزامنة تُطلق استثناءً غير متزامن فقط أثناء عمليات معينة في البرنامج. فعلى سبيل المثال، لا يؤثر استثناء Java java.lang.Thread::interrupt()على الخيط إلا عندما يستدعي الخيط عملية تُطلق استثناءً java.lang.InterruptedException. [ 57 ] أما واجهة برمجة تطبيقات POSIX المشابهة، فتُعاني من حالات تضارب تجعل استخدامها بأمان أمرًا مستحيلاً. [ 58 ]pthread_cancel

أنظمة التكييف

تحتوي لغات البرمجة Common Lisp و R و Dylan و Smalltalk على نظام شروط [ 60 ] (انظر نظام شروط Common Lisp ) يشمل أنظمة معالجة الاستثناءات المذكورة سابقًا. في هذه اللغات أو البيئات ، يُشير ظهور شرط ما (وهو "تعميم للخطأ" وفقًا لكينت بيتمان ) إلى استدعاء دالة، ولا يُمكن اتخاذ قرار فكّ مكدس الاستدعاءات إلا في مراحل متأخرة من معالج الاستثناءات.

الشروط هي تعميم للاستثناءات. عند حدوث شرط ما، يتم البحث عن معالج مناسب له واختياره، وفقًا لترتيب التسلسل، لمعالجة هذا الشرط. أما الشروط التي لا تمثل أخطاءً، فيمكن تركها دون معالجة تمامًا؛ إذ قد يكون غرضها الوحيد هو توجيه تلميحات أو تحذيرات إلى المستخدم. [ 61 ]

الاستثناءات المستمرة

يرتبط هذا بما يُسمى نموذج استئناف معالجة الاستثناءات، حيث تُعتبر بعض الاستثناءات قابلة للاستمرار : إذ يُسمح بالعودة إلى التعبير الذي أشار إلى الاستثناء، بعد اتخاذ إجراء تصحيحي في المعالج. يُعمم نظام الشروط على النحو التالي: ضمن معالج حالة غير خطيرة (تُعرف أيضًا بالاستثناء القابل للاستمرار )، يُمكن الانتقال إلى نقاط إعادة تشغيل مُحددة مسبقًا (تُعرف أيضًا بإعادة التشغيل ) تقع بين تعبير الإشارة ومعالج الحالة. إعادة التشغيل عبارة عن دوال مُغلقة على بيئة معجمية معينة، مما يسمح للمبرمج بإصلاح هذه البيئة قبل الخروج من معالج الحالة بالكامل أو حتى فكّ مكدس الاستدعاءات جزئيًا.

ومن الأمثلة على ذلك حالة ENDPAGE في PL/I؛ قد تقوم وحدة ON بكتابة أسطر تذييل الصفحة وأسطر رأس الصفحة التالية، ثم تتراجع لاستئناف تنفيذ التعليمات البرمجية المتقطعة.

إعادة التشغيل آلية منفصلة عن السياسة

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

مثال: لنفترض وجود دالة في مكتبة برمجية مهمتها تحليل مدخل واحد في ملف سجل النظام (syslog ). ماذا ينبغي لهذه الدالة أن تفعل إذا كان المدخل تالفًا؟ لا توجد إجابة صحيحة واحدة، لأن المكتبة نفسها قد تُستخدم في برامج لأغراض مختلفة. في متصفح ملفات السجل التفاعلي، قد يكون التصرف الأمثل هو إرجاع المدخل دون تحليل، ليتمكن المستخدم من رؤيته، أما في برنامج تلخيص السجلات الآلي، فقد يكون التصرف الأمثل هو توفير قيم فارغة للحقول غير المقروءة، مع إيقاف البرنامج مع إظهار خطأ، إذا كان عدد كبير جدًا من المدخلات تالفة.

بمعنى آخر، لا يمكن الإجابة على السؤال إلا في ضوء الأهداف العامة للبرنامج، والتي لا تعرفها دالة المكتبة العامة. ومع ذلك، فإن الخروج برسالة خطأ نادرًا ما يكون الحل الأمثل. لذا، بدلًا من الخروج برسالة خطأ، قد تُنشئ الدالة خيارات إعادة تشغيل تُتيح طرقًا مختلفة للمتابعة، مثل تخطي سجل الأحداث، أو توفير قيم افتراضية أو فارغة للحقول غير القابلة للقراءة، أو مطالبة المستخدم بالقيم المفقودة، أو إلغاء تتبع المكدس وإيقاف المعالجة برسالة خطأ. تُشكل خيارات إعادة التشغيل المتاحة الآليات اللازمة للتعافي من الخطأ؛ ويُحدد اختيار إعادة التشغيل بواسطة معالج الحالة السياسة المتبعة .

نقد

غالبًا ما يُساء التعامل مع الاستثناءات في البرمجيات، لا سيما عند وجود مصادر متعددة لها؛ فقد كشف تحليل تدفق البيانات لخمسة ملايين سطر من كود جافا عن أكثر من 1300 عيب في معالجة الاستثناءات. [ 13 ] وبالاستناد إلى دراسات سابقة متعددة أجراها باحثون آخرون (1999-2004) ونتائجهم الخاصة، كتب وايمر ونيكولا أن إحدى المشكلات الجوهرية في الاستثناءات هي أنها "تُنشئ مسارات تحكم خفية يصعب على المبرمجين فهمها". [ 13 ] : 8:27 "على الرغم من أن بنية try-catch-finally بسيطة من الناحية المفاهيمية، إلا أنها تحتوي على أكثر وصف تنفيذي تعقيدًا في مواصفات اللغة [Gosling et al. 1996] وتتطلب أربعة مستويات من عبارات "if" المتداخلة في وصفها الإنجليزي الرسمي. باختصار، تحتوي على عدد كبير من الحالات الشاذة التي غالبًا ما يتجاهلها المبرمجون." [ 13 ] : 8:13-8:14

تُزيد الاستثناءات، باعتبارها تدفقًا غير منظم، من خطر تسرب الموارد (مثل الخروج من قسم مُقفل بواسطة mutex ، أو قسم يحتفظ بملف مفتوح مؤقتًا) أو حالة غير متناسقة. توجد تقنيات متنوعة لإدارة الموارد في وجود الاستثناءات، وأكثرها شيوعًا هو الجمع بين نمط التخلص (dispose) ونوع من الحماية من التراجع (مثل finallyعبارة `rewind`)، والتي تُحرر المورد تلقائيًا عند الخروج من قسم من التعليمات البرمجية.

وصف توني هوار في عام 1980 لغة البرمجة آدا بأنها "تحتوي على عدد كبير من الميزات والاصطلاحات البرمجية، كثير منها غير ضروري، وبعضها، مثل معالجة الاستثناءات، خطير. [...] لا تسمحوا باستخدام هذه اللغة في وضعها الحالي في التطبيقات التي تُعد فيها الموثوقية أمرًا بالغ الأهمية [...]. قد لا يكون الصاروخ التالي الذي ينحرف عن مساره نتيجة خطأ في لغة البرمجة صاروخًا فضائيًا استكشافيًا في رحلة غير ضارة إلى كوكب الزهرة، بل قد يكون رأسًا نوويًا ينفجر فوق إحدى مدننا." [ 62 ]

يعتقد مطورو لغة Go أن أسلوب try-catch-finally يُعقّد تدفق التحكم ، [ 63 ] وقد قدموا آلية مشابهة للاستثناءات panic/ . [ 64 ] تختلف هذه الآلية عن غيرها في أنها لا يمكن استدعاؤها إلا من داخل كتلة برمجية في دالة، لذا يقتصر دور المعالج على التنظيف وتغيير قيم الإرجاع للدالة، ولا يمكنه إعادة التحكم إلى أي نقطة داخل الدالة. [ 65 ] وتعمل الكتلة نفسها بشكل مشابه لعبارة .recoverrecover()catchdeferdeferfinally

لا تحتوي لغة Rust على استثناءات. بدلاً من ذلك، تستخدم ( نوع نتيجة ) لمعالجة أخطاء وقت التشغيل، ويتم استخدام الماكرو للأخطاء الجسيمة.Result<T,E>panic!()

انظر أيضاً

ملحوظات

  1. في لغة PL/I على سبيل المثال، يؤدي الخروج العادي من معالج الاستثناءات إلى فك تسلسل المكدس.
  2. لا توجد تكلفة معالجة إلا في حالة عدم حدوث أي استثناء (مع وجود تكلفة ذاكرة نظرًا لحاجة جدول البحث إلى الذاكرة). توجد تكلفة (قد تكون كبيرة) في حالة حدوث استثناء (أي عندthrowتنفيذ العملية). قد يؤدي تطبيق معالجة الاستثناءات أيضًا إلى الحد من تحسينات المُصرّف الممكنة.
  3. لاحظ أنه تمت إزالة هذه الوظيفة في C++17 ، وتم إعادة تقديم الاسم لاحقًا في C++23 كفئةنوع نتيجةstd::unexpected<T, E> .

مراجع

  1. "الاستثناءات المدمجة - وثائق بايثون 3.10.4" . docs.python.org . تم الاطلاع عليه بتاريخ 17 مايو 2022 .
  2. ↑ بلوخ ، جوشوا (2008). "البند 57: استخدم الاستثناءات فقط في الحالات الاستثنائية" . جافا الفعالة ( الطبعة الثانية). أديسون-ويسلي. ص 241. ISBN   978-0-321-35668-0.
  3. 1 2 3 4 5 كينيري، جيه آر (2006). "الاستثناءات في جافا وإيفل: طرفان متناقضان في تصميم الاستثناءات وتطبيقها". مواضيع متقدمة في تقنيات معالجة الاستثناءات (ملف PDF) . سلسلة محاضرات في علوم الحاسوب. المجلد 4119. الصفحات 288-300 . doi : 10.1007/11818502_16 . ISBN   978-3-540-37443-5. S2CID 33283674 . 
  4. "Stroustrup: أسئلة وأجوبة حول أسلوب وتقنية لغة C++" . www.stroustrup.com . مؤرشف من الأصل بتاريخ 2 فبراير 2018. تم الاطلاع عليه بتاريخ 5 مايو 2018 .
  5. مكارثي، جون (12 فبراير 1979). "تاريخ لغة ليسب" . www-formal.stanford.edu . تم الاطلاع عليه بتاريخ 13 يناير 2022 .
  6. مكارثي، جون؛ ليفين، مايكل آي؛ أبراهامز، بول دبليو؛ إدواردز، دانيال جيه؛ هارت، تيموثي بي. (14 يوليو 1961). دليل مبرمج لغة ليسب 1.5 (ملف PDF) . تم الاطلاع عليه بتاريخ 13 يناير 2022 .
  7. "بيان التشغيل" (ملف PDF) . نظام التشغيل IBM System/360، مواصفات لغة PL/I (ملف PDF) . IBM. يوليو 1966. صفحة 120. C28-6571-3. 
  8. غابرييل وستيل 2008 ، ص 3.
  9. وايت 1979 ، ص 194.
  10. 1 2 ستروستروب 1994 ، ص. 392.
  11. "الاستثناءات - توثيق لمعالجة الاستثناءات في لغة بيرل" . MetaCPAN .
  12. ستروستروب، بيارن. "أسئلة وأجوبة حول أسلوب وتقنية لغة C++" . www.stroustrup.com . تم الاطلاع عليه بتاريخ 12 يناير 2022 .
  13. 1 2 3 4 وايمر، دبليو؛ نيكولا، جي سي (2008). "الحالات الاستثنائية وموثوقية البرنامج" (ملف PDF) . معاملات ACM في لغات البرمجة والأنظمة . المجلد 30، العدد 2. مؤرشف (ملف PDF) من الأصل بتاريخ 23-09-2015.  
  14. روبرتس، إريك س. (21 مارس 1989). تطبيق الاستثناءات في لغة C (ملف PDF) (تقرير فني). مركز أبحاث أنظمة DEC . SRC-RR-40 . تاريخ الاسترجاع: 4 يناير 2022 .
  15. كريستيانسن، توم؛ توركينغتون، ناثان (2003). "10.12. معالجة الاستثناءات". كتاب طبخ بيرل ( الطبعة الثانية). بكين: أورايلي. ISBN  0-596-00313-7.
  16. Stroustrup 1994 ، 16.6 معالجة الاستثناءات: الاستئناف مقابل الإنهاء، ص 390-393.
  17. "R: معالجة الحالات واستعادتها" . search.r-project.org . تم الاطلاع عليه بتاريخ 2022-12-05 .
  18. "std::uncaught_exception, std::uncaught_exceptions" . cppreference.com . cppreference . تم الاطلاع عليه بتاريخ 21 نوفمبر 2025 .
  19. سكوت مايرز ، أهم برامج لغة C++ على الإطلاق... مؤرشف في 28 أبريل 2011 على موقع Wayback Machine ، 2006
  20. D. Cameron, P. Faust, D. Lenkov, M. Mehta, "A portable implementation of C++ exception handling", Proceedings of the C++ Conference (August 1992) USENIX .
  21. بيتر كلايسنر (14 فبراير 2009). "معالجة استثناءات ويندوز - بيتر كلايسنر" . مؤرشف من الأصل في 14 أكتوبر 2013. تم الاطلاع عليه بتاريخ 21 نوفمبر 2009 .قسم معالجة الاستثناءات المهيكلة القائمة على المُصرّف
  22. غراهام هاتون، جويل رايت، " تجميع الاستثناءات بشكل صحيح " مؤرشف في 11-09-2014 على Wayback Machine . وقائع المؤتمر الدولي السابع حول رياضيات بناء البرامج ، 2004.
  23. لاجوا، جوزيه (مارس-أبريل 1994). "معالجة الاستثناءات - دعم آلية وقت التشغيل". تقرير C++ . 6 (3).
  24. 1 2 شيلينغ، جوناثان ل. (أغسطس 1998). "تحسين معالجة الاستثناءات في لغة C++" . إشعارات SIGPLAN . 33 (8): 40-47 . doi : 10.1145/286385.286390 . S2CID 1522664 . 
  25. "أفضل الممارسات الحديثة في لغة C++ للتعامل مع الاستثناءات والأخطاء" . مايكروسوفت . 8 مارس 2021. تم الاطلاع عليه بتاريخ 21 مارس 2022 .
  26. 1 2 ستروستروب، بيارن (18 نوفمبر 2019). "استثناءات وبدائل لغة C++" (ملف PDF) . تم الاطلاع عليه بتاريخ 23 مارس 2022 .
  27. M. Hof, H. Mössenböck, P. Pirkelbauer, " معالجة الاستثناءات بدون تكلفة إضافية باستخدام البرمجة الوصفية مؤرشفة في 2016-03-03 على Wayback Machine وقائع SOFSEM'97 ، نوفمبر 1997، سلسلة محاضرات في علوم الحاسوب 1338 ، ص 423-431.
  28. "عقود لغة C++" (ملف PDF) . 13 فبراير 2025.
  29. يتم التعامل مع جميع الاستثناءات، جيم ويلكوكس، "يتم التعامل مع جميع الاستثناءات" . 22 فبراير 2008.
  30. 1 2 3 مكتبة مطوري ماك ، " استثناءات غير معالجة مؤرشفة بتاريخ 2016-03-04 في Wayback Machine "
  31. MSDN ، حدث AppDomain.UnhandledException مؤرشف بتاريخ 4 مارس 2016 على موقع Wayback Machine
  32. دليل بايثون التعليمي ، " 8. الأخطاء والاستثناءات " مؤرشف بتاريخ 2015-09-01 في Wayback Machine
  33. "ممارسات جافا -> توفير معالج استثناءات غير معالجة" . www.javapractices.com . مؤرشف من الأصل في 9 سبتمبر 2016. تم الاطلاع عليه في 5 مايو 2018 .
  34. PyMOTW (وحدة بايثون لهذا الأسبوع)، " معالجة الاستثناءات " مؤرشفة بتاريخ 15-09-2015 على موقع Wayback Machine
  35. "إجابات جوجل: أصل الاستثناءات المُدققة" . مؤرشف من الأصل بتاريخ 2011-08-06 . تم الاطلاع عليه بتاريخ 2011-12-15 .
  36. مواصفات لغة جافا، الفصل 11.2. http://java.sun.com/docs/books/jls/third_edition/html/exceptions.html#11.2 مؤرشف بتاريخ 8 ديسمبر 2006 في أرشيف الإنترنت (Wayback Machine).
  37. ^ موسنبوك ، هانسبيتر (2002-03-25). ""C# المتقدم: عدد متغير من المعلمات"" (PDF) . معهد برمجيات النظم، جامعة يوهانس كيبلر لينز، Fachbereich Informatik. ص. 32. أرشفة (PDF) من النسخة الأصلية بتاريخ 20-09-2011 . تم الاسترجاع 2011/08/05 . 
  38. 1 2 3 إيكل، بروس (2006). التفكير بلغة جافا ( الطبعة الرابعة). أبر سادل ريفر، نيوجيرسي: برنتيس هول. الصفحات 347-348 . ISBN   0-13-187248-6.
  39. غونرسون، إريك (9 نوفمبر 2000). "لغة سي شارب ومواصفات الاستثناءات" . مؤرشف من الأصل في 1 يناير 2006.
  40. 1 2 بيل فينرز؛ بروس إيكل (18 أغسطس 2003). "مشكلة الاستثناءات المُدققة: حوار مع أندرس هيلسبرغ، الجزء الثاني" . تم الاطلاع عليه في 4 يناير 2022 .
  41. جونو، جوش (31 مايو 2017). وصفات جافا 9: منهج حل المشكلات . دار نشر أبريس. ص 226. ISBN  978-1-4842-1976-8.
  42. "مزايا الاستثناءات (سلسلة دروس جافا: الفئات الأساسية: الاستثناءات)" . Download.oracle.com. مؤرشف من الأصل بتاريخ 26-10-2011 . تم الاطلاع عليه بتاريخ 15-12-2011 .
  43. "الاستثناءات غير المُعالجة - الجدل (سلسلة دروس جافا: الفئات الأساسية: الاستثناءات)" . Download.oracle.com. مؤرشف من الأصل بتاريخ 17-11-2011 . تم الاطلاع عليه بتاريخ 15-12-2011 .
  44. بلوخ 2001:178 بلوخ، جوشوا (2001). دليل فعال للغة برمجة جافا . أديسون-ويسلي بروفيشنال. ISBN 978-0-201-31005-4.
  45. 1 2 "شركة MindView التابعة لبروس إيكل: هل تحتاج جافا إلى استثناءات مُدققة؟" . Mindview.net. مؤرشف من الأصل بتاريخ 2002-04-05 . تم الاطلاع عليه بتاريخ 2011-12-15 .
  46. ليسكوف، ب.هـ؛ سنايدر، أ. (نوفمبر 1979). "معالجة الاستثناءات في لغة CLU" (ملف PDF) . معاملات IEEE في هندسة البرمجيات . SE-5 (6): 546–558 . Bibcode : 1979ITSEn...5..546L . doi : 10.1109/TSE.1979.230191 . S2CID 15506879. تاريخ الاسترجاع: 19 ديسمبر 2021 . 
  47. "Modula-3 - أنواع الإجراءات" . cs.columbia.edu. 1995-03-08. مؤرشف من الأصل في 2008-05-09 . تم الاطلاع عليه في 2011-12-15 .
  48. 1 2 بيارن ستروستروب ، لغة البرمجة سي++ ، الطبعة الثالثة، أديسون ويسلي ، 1997. ISBN 0-201-88954-4الصفحات 375-380.
  49. ريفز، جيه دبليو (يوليو 1996). "عشرة إرشادات لمواصفات الاستثناءات". تقرير سي++ . 8 (7).
  50. ساتر، هيرب (3 مارس 2010). "تقرير رحلة: اجتماع معايير ISO C++ لشهر مارس 2010" . مؤرشف من الأصل في 23 مارس 2010. تم الاطلاع عليه في 24 مارس 2010 .
  51. "OcamlExc - محلل استثناءات غير معالجة للغة Objective Caml" . Caml.inria.fr. مؤرشف من الأصل بتاريخ 2011-08-06 . تم الاطلاع عليه بتاريخ 2011-12-15 .
  52. "std::result - Rust" . doc.rust-lang.org . مؤرشف من الأصل بتاريخ 2023-10-09 . تم الاطلاع عليه بتاريخ 2023-10-09 .
  53. "stdlib: إضافة وحدة نتائج · rust-lang/rust@c1092fb" . github.com . 2011-10-29. مؤرشف من الأصل في 2023-10-09 . تم الاسترجاع في 2023-10-09 .
  54. "الاستثناءات غير المتزامنة في لغة هاسكل - مارلو، جونز، موران (فهرس الأبحاث)" . Citeseer.ist.psu.edu. مؤرشف من الأصل بتاريخ 23 فبراير 2011. تم الاطلاع عليه بتاريخ 15 ديسمبر 2011 .
  55. فروند، ستيفن ن.؛ ميتشل، مارك ب. الاستثناءات غير المتزامنة الآمنة للغة بايثون (ملف PDF) (تقرير فني) . تم الاطلاع عليه بتاريخ 4 يناير 2022 .
  56. "إيقاف استخدام أنواع البيانات الأولية في سلاسل العمليات في جافا" . Java.sun.com. مؤرشف من الأصل بتاريخ 26-04-2009 . تم الاطلاع عليه بتاريخ 15-12-2011 .
  57. "المقاطعات (دروس جافا™ > فئات جافا الأساسية > التزامن)" . docs.oracle.com . تم الاطلاع عليه في 5 يناير 2022 .
  58. فيلكر، ريتش. "إلغاء الخيوط وتسريب الموارد" . ewontfix.com . تم الاطلاع عليه في 5 يناير 2022 .
  59. "R: معالجة الحالات واستعادتها" . search.r-project.org . تم الاطلاع عليه بتاريخ 25-03-2024 .
  60. ما هي حقيقة الشروط (الاستثناءات) (24 مارس 2008). "ما هي حقيقة الشروط (الاستثناءات)" . Danweinreb.org. مؤرشف من الأصل في 1 فبراير 2013. تم الاطلاع عليه بتاريخ 18 سبتمبر 2014 .
  61. "9.1 مفاهيم نظام الشروط" . Franz.com. 25-07-2022. مؤرشف من الأصل في 07-06-2024 . تم الاطلاع عليه في 07-06-2024 .
  62. كار هوار. "ملابس الإمبراطور القديمة". محاضرة جائزة تورينج لعام 1980
  63. "الأسئلة الشائعة" . مؤرشف من الأصل بتاريخ 3 مايو 2017. تم الاطلاع عليه بتاريخ 27 أبريل 2017. نعتقد أن ربط الاستثناءات ببنية تحكم، كما في نمط try-catch-finally، يؤدي إلى شيفرة معقدة. كما أنه يشجع المبرمجين على تصنيف الكثير من الأخطاء العادية، مثل فشل فتح ملف، على أنها استثنائية.
  64. الذعر والتعافي ( مؤرشف بتاريخ ٢٤ أكتوبر ٢٠١٣ في أرشيف الإنترنت ، ويكيبيديا)
  65. بيندرسكي، إيلي (8 أغسطس 2018). "حول استخدامات وإساءة استخدام حالات الذعر في لغة Go" . موقع إيلي بيندرسكي الإلكتروني . تم الاطلاع عليه في 5 يناير 2022. يتمثل القيد المحدد في أنه لا يمكن استدعاء دالة recover إلا في كتلة برمجية defer، والتي لا يمكنها إعادة التحكم إلى نقطة عشوائية، بل يمكنها فقط إجراء عمليات تنظيف وتعديل قيم الإرجاع للدالة.

المراجع