الإمساك بعدة استثناءات وإعادة الرمي
الإمساك بعدة استثناءات وإعادة الرمي
بحلول الدرس الخامس، تعرف كيف ترمي الاستثناءات وتُعلن عنها. يتناول هذا الدرس أسلوبَين مترابطَين يجعلان شيفرة معالجة الأخطاء أنظف وأكثر تعبيرًا: الإمساك بعدة أنواع من الاستثناءات في كتلة واحدة، وإعادة رمي الاستثناء — أو تغليفه — لكي تتلقى كل طبقة في برنامجك النوع المناسب من الخطأ.
مشكلة تكرار كتل catch
لنفترض أنك تحلّل مدخلات المستخدم وتكتب في ملف. قد تفشل العمليتان بطرق مختلفة، لكن إجراء التعافي واحد: تسجيل المشكلة وإعادة قيمة افتراضية. النهج الساذج يؤدي إلى تكرار:
قدّمت Java 7 تعدد الإمساك multi-catch للقضاء على هذا التكرار.
صيغة multi-catch
افصل أنواع الاستثناءات بشارة الأنبوب (|) داخل جملة catch واحدة. المعامل يصبح ضمنيًا final.
النوعان غير مترابطَين في التسلسل الهرمي، ومع ذلك يشتركان في معالج واحد. يستنتج Java النوع المشترك الأكثر خصوصية — هنا Exception — ولهذا يكون المعامل ضمنيًا final: لا يمكنك إعادة تعيين مرجع لا يمكن للمترجم التنبؤ بنوعه الدقيق في وقت التشغيل.
catch المتسلسلة، الأنواع في قائمة الأنبوب لا تُقيَّم من الأعلى للأسفل. جميعها تُعامَل على قدم المساواة. لا يمكنك إدراج نوع أب مع أحد أنواعه الفرعية (مثل Exception | IOException) لأن ذلك يجعل النوع الفرعي غير قابل للوصول؛ سيرفضها المترجم بخطأ في وقت الترجمة.
متى يكون multi-catch الأداة المناسبة
- منطق التعافي متطابق تمامًا لجميع الأنواع المدرجة.
- الأنواع غير مترابطة (لا تربطها علاقة أب-ابن).
- لا تزال تريد الحفاظ على الاستثناء الأصلي في سجل أو سلسلة.
إذا اختلفت المعالجة — مثلًا تريد إعادة المحاولة عند IOException لكن ليس عند NumberFormatException — فاستخدم كتلًا منفصلة.
إعادة رمي الاستثناء نفسه
أحيانًا تمسك بالاستثناء فقط لإضافة سطر سجل أو تحرير مورد، ثم تتركه يتصاعد دون تغيير. تفعل ذلك بـ throw e; عادي:
منذ Java 7، يكون المترجم ذكيًا بما يكفي ليعرف أن IOException فقط هي التي يمكن رميها من كتلة try، لذا حتى لو كان نوع معامل الإمساك Exception، يُسمح بإعادة رميه في دالة مُعلنة بـ throws IOException. يُسمى هذا إعادة الرمي الدقيق.
تغليف الاستثناءات (التسلسل)
حين يتسرب استثناء منخفض المستوى من دالة عالية المستوى، يضطر المستدعي لمعرفة تفصيلة تنفيذية لا ينبغي أن يعرفها. الحل هو تغليف الاستثناء الأصلي داخل استثناء جديد يناسب مستوى التجريد، مع الحفاظ على السبب لأغراض التنقيح.
صيغة البنّاء المستخدمة أعلاه هي الصيغة القياسية للاستثناءات القابلة للتغليف:
الآن تتضمن تتبع المكدس المطبوع بـ e.printStackTrace() كلًا من RepositoryException عالية المستوى والـ SQLException الأصلية تحت عنوان "Caused by:"، مما يمنحك كل ما تحتاجه للتنقيح دون كشف طبقة SQL للمستدعين.
throw new RepositoryException("Could not load user") — دون e — تُهدر السبب الجذري بصمت وتجعل تتبع الأخطاء في الإنتاج صعبًا جدًا.
إعادة الرمي داخل multi-catch
يمكن دمج الأسلوبين معًا. في المثال التالي، تمسك دالة مساعدة بنوعَين غير مترابطَين، تسجّلهما بمعالج واحد، ثم تعيد رمي كل منهما مغلَّفًا كاستثناء نطاق:
catch (Exception e) { throw e; } — فاحذف الكتلة كليًا. إعادة الرمي بلا فائدة تُشوّش الشيفرة وقد تغيّر التزامات الاستثناءات المحددة/غير المحددة بطرق تفاجئ المستدعين.
الخلاصة
- Multi-catch (
catch (A | B e)) يُزيل المعالجات المكررة حين يكون التعافي متطابقًا؛ يجب أن تكون الأنواع غير مترابطة في التسلسل الهرمي. - إعادة الرمي تتيح لطبقة ما تسجيل أو تحرير موارد ثم تمرير الاستثناء للأعلى دون تغيير.
- تغليف الاستثناءات يترجم استثناءً منخفض المستوى إلى استثناء يناسب طبقة التجريد الحالية، مع استخدام معامل
causeفي البنّاء للحفاظ على الأصل لأغراض التنقيح. - أدرج دائمًا الاستثناء الأصلي كسبب عند التغليف — لا تُهدره بصمت أبدًا.