السبب في أن الاستثناءات هي طريقة تصحيح أخطاء قوية هو أنها تجيب على الأسئلة الثلاثة التالية:
1. ما الخطأ الذي حدث؟
2. أين ارتكبت خطأ؟
3. لماذا حدث خطأ ما؟
في حالة الاستخدام الفعال للاستثناءات ، يجيب نوع الاستثناء على "ماذا" ، وأجوبة تتبع مكدس الاستثناء "حيث" يتم إلقاؤها ، وإجابات معلومات الاستثناء "لماذا" ، وإذا لم يرد استثناءك على جميع الأسئلة المذكورة أعلاه ، فلا يجوز لك استخدامها جيدًا.
هناك ثلاثة مبادئ يمكن أن تساعدك على زيادة استخدام استثناءات جيدة أثناء تصحيح الأخطاء. هذه المبادئ الثلاثة هي:
1. محدد وواضح
2. رميها مبكرًا
3. تأخير التقاط
من أجل شرح هذه المبادئ الثلاثة للتعامل مع الاستثناءات الفعالة ، تناقشها هذه المقالة من خلال تصنيع مدير مالي مالي JCHECKBOOK ، والذي يستخدم لتسجيل وتتبع أنشطة الحساب المصرفي مثل الودائع والسحب وإصدار الفاتورة.
ملموسة وواضحة
تحدد Java تسلسلًا هرميًا لفئة استثناء ، والتي تبدأ مع قابلية للرمي ، يمتد إلى الخطأ والاستثناء ، والاستثناء يمتد RunTimeException. كما هو مبين في الشكل 1.
هذه الفئات الأربعة معممة ولا توفر الكثير من معلومات الخطأ. على الرغم من أن إنشاء هذه الفئات قانونية من الناحية النحوية (مثل: New Throwable ()) ، فمن الأفضل معاملتها كطبقات قاعدة افتراضية واستخدام فئاتهم الفرعية الأكثر تخصصًا. قدمت Java عددًا كبيرًا من الفئات الفرعية الاستثناء. إذا كنت تريد أن تكون أكثر تحديداً ، فيمكنك أيضًا تحديد فئة الاستثناء الخاصة بك.
على سبيل المثال: تحدد حزمة Java.io الفئة الفرعية لفئة الاستثناء IoException ، والتي هي أكثر تخصصًا وفرعًا من IoException مثل FileNotfoundException و EofException و ObjectStreamException. يصف كل منهما فئة محددة من أخطاء الإدخال/الإخراج: فقدان الملف وإنهاء ملف الاستثناء ودفقات الكائنات المسلسل للخطأ. كلما كان برنامجنا أكثر تحديداً ، سيكون برنامجنا قادرًا على الإجابة على السؤال "ما الخطأ".
من المهم أيضًا محاولة أن تكون واضحًا قدر الإمكان عند التقاط الاستثناءات. على سبيل المثال: يمكن لـ jCheckBook التعامل مع FileNotfoundException عن طريق إعادة استجواب اسم ملف المستخدم. بالنسبة إلى EofException ، يمكن أن تستمر في التشغيل بناءً على المعلومات التي تقرأ قبل إلقاء الاستثناء. إذا تم إلقاء ObjectStreamException ، فيجب على البرنامج أن يطالب المستخدم بأن الملف تالف ويجب استخدام ملف احتياطي أو ملف آخر.
تجعل Java من السهل التقاط استثناءات بشكل صريح ، لأنه يمكننا تحديد كتل الصيد المتعددة لنفس كتلة المحاولة ، بحيث يمكن التعامل مع كل استثناء بشكل منفصل.
ملف prefsfile = ملف جديد (prefsfilename) ؛ جرب {readpreferences (prefsfile) ؛} catch (fileNotfoundException e) {// قم بتنبيه المستخدم بأن الملف المحدد // غير موجود} catch (eofexception e) {// تنبيه المستخدم الذي تم التوصل إليه (expision e}} المستخدم الذي حدث خطأ آخر I/O //}يوفر JCheckBook للمستخدمين معلومات واضحة حول التقاط الاستثناءات باستخدام كتل اصطياد متعددة. على سبيل المثال: إذا تم اكتشاف FileNotFoundException ، فيمكنه مطالبة المستخدم بتحديد ملف آخر. في بعض الحالات ، قد يكون جهد الترميز الإضافي الذي تُعرفه عن العديد من كتل الصيد بمثابة عبء غير ضروري ، ولكن في هذا المثال ، فإن الكود الإضافي يساعد البرنامج على توفير استجابة أكثر سهولة في الاستخدام.
بالإضافة إلى الاستثناءات التي تم التعامل معها بواسطة كتل الصيد الثلاثة الأولى ، توفر كتلة الصيد الأخيرة للمستخدم معلومات خطأ أكثر تعميمًا عند إلقاء IOException. وبهذه الطريقة ، يمكن للبرنامج تقديم معلومات محددة قدر الإمكان ، ولكن لديه أيضًا القدرة على التعامل مع استثناءات أخرى غير متوقعة.
في بعض الأحيان ، يلتقط المطورون استثناءات طبيعية وعرضون اسم فئة الاستثناء أو معلومات مكدس الطباعة لـ "الخصوصية". لا تفعل هذا! لن يكون لدى المستخدمين سوى صداع عند رؤية java.io.EOFException أو مكدس المعلومات ، بدلاً من الحصول على المساعدة. يجب التقاط استثناءات محددة ويجب مطالب المستخدم بـ "الكلمات البشرية". ومع ذلك ، يمكن طباعة مكدس الاستثناء في ملف السجل الخاص بك. تذكر أن الاستثناءات ومعلومات المكدس تستخدم لمساعدة المطورين بدلاً من المستخدمين.
أخيرًا ، تجدر الإشارة إلى أن JCHECKBOOK لا يلتقط استثناءات في readPreferences() ، ولكن يترك استثناءات CAST والتعامل مع طبقة واجهة المستخدم ، بحيث يمكن إخطار المستخدمين باستخدام مربعات الحوار أو طرق أخرى. وهذا ما يسمى "تأخير التقاط" ، والتي سيتم مناقشتها أدناه.
رمي مبكرا
توفر معلومات مكدس الاستثناء الترتيب الدقيق لسلسلة استدعاء الطريقة التي تتسبب في حدوث الاستثناء ، بما في ذلك اسم الفئة واسم الطريقة واسم ملف الرمز وحتى رقم سطر كل استدعاء طريقة ، من أجل تحديد موقع المشهد بدقة حيث يحدث الاستثناء.
java.lang.nullpointerxceptionat java.io.fileinputstream.open (الطريقة الأصلية) في java.io.fileinputstream. <Ing> (fileInputStream.java:103) في jcheckbook.jcheckbook.readpreferences (jcheckbook.java:225) jcheckbook.jcheckbook.startup (jcheckbook.java:116) في jcheckbook.jcheckbook. <Ing> (jcheckbook.java:27) في jcheckbook.jcheckbook.main (jcheckbook.java:318)
يوضح ما ورد أعلاه الحالة التي ترمي فيها طريقة Open () لفئة FileInputStream a nullpointerxception. ومع ذلك ، لاحظ أن FileInputStream.close() جزء من مكتبة فئة Java القياسية ، ومن المحتمل أن تكون مشكلة هذا الاستثناء هي أن الكود نفسه ليس API Java. لذلك من المحتمل أن تظهر المشكلة في إحدى الطرق السابقة ، ولحسن الحظ يتم طباعتها أيضًا في معلومات المكدس.
لسوء الحظ ، فإن NullPointerException هو الاستثناء الأقل إفادة (ولكن أيضًا الأكثر شيوعًا والتعطل) في Java. لا يذكر أكثر ما نهتم به على الإطلاق: أين هو لاغ. لذلك كان علينا أن نأخذ بضع خطوات إلى الوراء ومعرفة أين حدث خطأ ما.
من خلال دعم معلومات مكدس التتبع والتحقق من الرمز ، يمكننا تحديد أن سبب الخطأ هو أنه تم تمرير معلمة اسم ملف فارغ في readPreferences() . نظرًا لأن readPreferences() يعرف أنه لا يمكنه التعامل مع أسماء الملفات الفارغة ، تحقق من الشرط على الفور:
REWPREFERENCES public void (اسم ملف السلسلة) يلقي alfortleargumentException {if (fileName == null) {رمي جديد غير alfictalargumentException ("اسم fileName null") ؛ } // if //...perform العمليات الأخرى ... inputStream in = new FileInputStream (filename) ؛ //... اقرأ ملف التفضيلات ...} من خلال إلقاء استثناء مبكرًا (المعروف أيضًا باسم "الفشل بسرعة") ، يكون الاستثناء واضحًا ودقيقًا. تعكس معلومات المكدس على الفور الخطأ الذي حدث (تم توفير قيم المعلمات غير القانونية) ، ولماذا حدث خطأ (لا يمكن أن يكون اسم الملف فارغًا) ، وحيثما حدث خطأ (الجزء الأول من readPreferences() ). وبهذه الطريقة ، يمكن توفير معلومات المكدس لدينا بصدق:
java.lang.illegalargumentexception: اسم الملف هو nullat jcheckbook.jcheckbook.readpreferences (jcheckbook.java:207) في jcheckbook.jcheck. jcheckbook.jcheckbook.main (jcheckbook.java:318)
بالإضافة إلى ذلك ، فإن معلومات الاستثناء الواردة في تكنولوجيا المعلومات ("اسم الملف فارغ") تجعل الاستثناء أكثر ثراءً من خلال الإجابة بشكل صريح على مسألة ما هو فارغ ، وهو غير متاح لنا في الكود السابق.
يتم تحقيق الفشل من خلال إلقاء استثناء فورًا عند اكتشاف خطأ ، يمكن تجنب بناء الكائنات غير الضرورية أو استخدام الموارد ، مثل اتصال الملف أو الشبكة ، بشكل فعال. وبالمثل ، يمكن حفظ عمليات التنظيف التي أدت إلى فتح هذه الموارد.
تأخر التقاط
أحد الخطأ الذي يمكن أن يرتكبه كل من المبتدئين والماجستير هو التقاط البرنامج قبل أن يكون لديه القدرة على التعامل مع الاستثناء. يسهل برنامج التحويل البرمجي Java هذا السلوك بشكل غير مباشر من خلال مطالبة الاستثناء الذي تم فحصه أو إلقاؤه. تتمثل الطريقة الطبيعية في لف الرمز على الفور في كتلة المحاولة واستخدام Catch للقبض على استثناءات لتجنب خطأ المترجم.
والسؤال هو ، ماذا علي أن أفعل إذا حصلت على الاستثناء بعد القبض عليه؟ أسوأ شيء يجب فعله هو عدم القيام بأي شيء. تعادل كتلة الصيد الفارغة رمي الاستثناء بالكامل في الثقب الأسود ، وجميع المعلومات التي يمكن أن تشرح متى ، أين ، لماذا تخطئ الأخطاء إلى الأبد. من الأفضل أن تكتب استثناءات في السجل ، على الأقل هناك سجلات للتحقق منها. لكن لا يمكننا أن نتوقع من المستخدمين قراءة أو فهم ملفات السجل ومعلومات الاستثناء. كما أنه ليس من المناسب أيضًا عرض readPreferences() حوار رسالة خطأ ، لأنه على الرغم من أن JCheckBook هو حاليًا تطبيق سطح المكتب ، فإننا نخطط أيضًا لتحويله إلى تطبيق ويب يستند إلى HTML. في هذه الحالة ، من الواضح أن عرض مربع حوار خطأ ليس خيارًا. في الوقت نفسه ، بغض النظر عن إصدار HTML أو C/S ، تتم قراءة معلومات التكوين على الخادم ، ويجب عرض رسالة الخطأ إلى متصفح الويب أو برنامج العميل. يجب أن تأخذ readPreferences() أيضًا هذه الاحتياجات المستقبلية في الاعتبار عند التصميم. يمكن للفصل الصحيح لرمز واجهة المستخدم ومنطق البرنامج تحسين قابلية إعادة استخدام الكود لدينا.
غالبًا ما يؤدي التقاط استثناء قبل الأوان قبل التعامل معه إلى أخطاء أكثر خطورة واستثناءات أخرى. على سبيل المثال ، إذا كانت طريقة readPreferences() أعلاه تلتقط وتسجيل FileNotfoundException التي يمكن إلقاؤها عند استدعاء مُنشئ FileInputStream ، فسيصبح الرمز مثل هذا:
public void retpreferences (اسم ملف السلسلة) {// ... inputStream in = null ؛ // لا تفعل هذا !!! جرب {in = new FileInputStream (filename) ؛} catch (fileNotFoundException e) {logger.log (e) ؛} in.read (...) ؛ // ...}يلتقط الكود أعلاه دون القدرة على التعافي من FileNotfoundException. إذا تعذر العثور على الملف ، فمن الواضح أن الطريقة التالية لا يمكن قراءتها. ماذا يحدث إذا طُلب من إعادة الشراء () قراءة ملف غير موجود؟ بالطبع ، سيتم تسجيل FileNotfoundException ، وإذا نظرنا إلى ملف السجل في ذلك الوقت ، فسنعلم. ومع ذلك ، ماذا يحدث عندما يحاول البرنامج قراءة البيانات من ملف؟ نظرًا لأن الملف غير موجود ، فإن المتغير في فارغ ، وسيتم طرح nullpointerxception.
عند تصحيح البرنامج ، تخبرنا غريزة أن ننظر إلى المعلومات في نهاية السجل. سيكون ذلك nullpointerxception ، وما هو مزعج للغاية هو أن هذا الاستثناء غير محدد للغاية. رسالة الخطأ لا تضللنا فقط ما حدث خطأ (الخطأ الحقيقي هو FileNotfoundException بدلاً من NullPointerException) ، ولكن أيضًا يضلل مصدر الخطأ. المشكلة الحقيقية هي خارج عدد الصفوف في NullPointerException التي تم إلقاؤها ، والتي قد تحتوي على العديد من مكالمات الطريقة وتدمير الطبقة. تم لفت انتباهنا من الخطأ الحقيقي من قبل الأسماك الصغيرة حتى نظرنا إلى الوراء إلى السجل للعثور على مصدر المشكلة.
نظرًا لأن ما readPreferences() يجب فعله في الواقع هو عدم التقاط هذه الاستثناءات ، فماذا يجب أن يكون؟ يبدو أنه غير بديهي بعض الشيء ، والطريقة الأكثر ملاءمة هي عدم القيام بأي شيء وعدم التقاط استثناءات على الفور. اترك مسؤولية متصل readPreferences() واتركها تدرس الطريقة المناسبة للتعامل مع ملفات التكوين المفقودة. قد يطالب المستخدم بتحديد الملفات الأخرى ، أو استخدام القيمة الافتراضية. إذا لم ينجح الأمر حقًا ، فقد يحذر المستخدم والخروج من البرنامج.
تتمثل طريقة اجتياز مسؤولية التعامل مع استثناءات سلسلة المكالمات في الإعلان عن استثناء جملة THROWS للطريقة. عند الإعلان عن الاستثناءات المحتملة ، كن حذرًا ، كلما كان ذلك أفضل. يتم استخدام هذا لتحديد نوع الاستثناء الذي يحتاجه البرنامج الذي يتصل بالطريقة الخاصة بك إلى المعرفة والاستعداد للتعامل معها. على سبيل المثال ، قد يبدو إصدار "Delay Capture" من readPreferences() هكذا:
REDPREFERENCES public void (اسم ملف السلسلة) يلقي غير alualtalArgumentException ، fileNotfoundException ، ioException {if (fileName == null) {رمي غير alfictalargumentexception ("filename is null") ؛ } // if // ... inputStream in = new FileInputStream (filename) ؛ // ...}من الناحية الفنية ، فإن الاستثناء الوحيد الذي نحتاج إلى إعلانه هو IoException ، لكننا نعلن صراحة أن الطريقة قد ترمي filenotfoundException. ليس من الضروري الإعلان عن إعلان alfectalArgumentException لأنه استثناء غير متخلف (على سبيل المثال ، فئة فرعية من RunTimeException). ومع ذلك ، يُعلن توثيق رمزنا (يجب أيضًا وضع علامة على هذه الاستثناءات في Javadocs للطريقة).
بالطبع ، يحتاج البرنامج في النهاية إلى استثناء الاستثناء ، وإلا فإنه سينتهي بشكل غير متوقع. ولكن الحيلة هنا هي التقاط استثناءات على المستوى الصحيح حتى يتمكن البرنامج إما بشكل مفيد من الاستثناءات والاستمرار دون التسبب في أخطاء أعمق ؛ أو أن تكون قادرًا على تزويد المستخدمين بمعلومات واضحة ، بما في ذلك توجيههم للتعافي من الأخطاء. إذا لم تكن طريقتك مختصة ، فلا تتعامل مع الاستثناء ، اتركها وراءها للقبض عليها والتعامل معها على المستوى الصحيح.
لخص
يعلم المطورين ذوي الخبرة أن أكبر صعوبة في برامج تصحيح الأخطاء هي عدم إصلاح العيوب ، ولكن لمعرفة أماكن إخفاء العيوب من كمية هائلة من التعليمات البرمجية. طالما أنك تتبع المبادئ الثلاثة لهذه المقالة ، يمكن أن تساعدك استثناءاتك في تتبع العيوب والتخلص منها ، وجعل البرنامج أكثر قوة وسهولة في الاستخدام. ما سبق هو المحتوى الكامل لهذه المقالة ، وآمل أن يساعدك في دراستك أو عملك.