كلاسات الاستثناءات المخصصة
كلاسات الاستثناءات المخصصة
الاستثناءات التي تأتي مع Java — مثل IllegalArgumentException وIOException وNullPointerException — استثناءات عامة. حين تقرأ في stack trace رسالة مثل IllegalArgumentException: invalid value لا تزال مضطرًا لتخمين أيّ قيمة كانت خاطئة ولماذا. كلاسات الاستثناءات المخصصة تحلّ هذه المشكلة: تعطي اسمًا دقيقًا للمشكلة، وتنتمي إلى نطاق عملك (domain)، وتستطيع حمل سياق إضافي لا تستطيعه الاستثناءات العامة.
لماذا تُنشئ استثناءاتك الخاصة؟
- أسماء ذات معنى.
InsufficientFundsExceptionيوصل المشكلة فورًا؛ بينماRuntimeException: balance too lowلا يوصلها بنفس الوضوح. - التقاط انتقائي. يستطيع المُستدعي (caller) التقاط الفشل المحدد الذي يعرف كيف يتعامل معه دون أن يُخمد استثناءات أخرى غير ذات صلة.
- حمل بيانات النطاق. يستطيع الكلاس المخصص تخزين رقم الحساب والمبلغ المطلوب والرصيد الحالي — لا مجرد رسالة نصية.
- واجهات برمجية أنظف. توقيع دالة مثل
throws InsufficientFundsExceptionيوثّق القصد بشكل أفضل بكثير منthrows Exception.
توسيع Exception مقابل RuntimeException
هذا هو أهمّ قرار في التصميم. تنطبق هنا القاعدة التي درستها في الدرس الرابع:
- وسّع
Exception(استثناء محقق) حين ينبغي للمُستدعي معالجة الفشل — ملف غير موجود، انتهاء مهلة الشبكة، انتهاكات قواعد عمل يمكن التعافي منها. - وسّع
RuntimeException(استثناء غير محقق) حين يكون الفشل خطأ برمجيًا — وسيط غير صالح، حالة خاطئة، عقد مكسور — لا يجب على المُستدعي التقاطه في العادة.
InsufficientFundsException extends Exception هو الخيار الصحيح. أما إذا استقبلت أداة داخلية قيمة null لا ينبغي أن تصلها أبدًا، فـextends RuntimeException أنسب.
النمط الأدنى
يحتاج كل استثناء مخصص على الأقل إلى مُنشئ (constructor) يقبل رسالة ويمرّرها للكلاس الأب:
هذا كل ما تحتاجه لرمي الاستثناء والتقاطه بالاسم:
إضافة مُنشئ للسبب (cause)
احرص دائمًا على توفير مُنشئ يقبل Throwable كسبب. يتيح ذلك تغليف استثناءات المستوى الأدنى دون فقدان stack trace الأصلي — وهي ممارسة تعلمتها في الدرس السادس (إعادة الرمي).
تخزين بيانات خاصة بالنطاق
يمكن للكلاس المخصص الاحتفاظ بأي حقول تساعد المُستدعين على الاستجابة بذكاء للفشل:
يستطيع المُستدعي الآن عرض رسالة مفيدة للمستخدم دون الحاجة إلى تحليل نص:
بناء تسلسل هرمي صغير للاستثناءات
في التطبيقات الأكبر، يمكنك تجميع الاستثناءات ذات الصلة تحت قاعدة مشتركة ليتمكن المُستدعون من التقاط الأخطاء بشكل واسع أو ضيق حسب الحاجة:
يستطيع معالج بوابة الدفع التقاط جميع مشاكل الدفع بـcatch (PaymentException e)، بينما تستطيع وحدة الاسترداد التي تهتم فقط بالبطاقات المرفوضة التقاط CardDeclinedException تحديدًا.
اتفاقية التسمية
يجب أن ينتهي اسم كل كلاس استثناء مخصص بـException — مثل ValidationException وOrderNotFoundException وRateLimitExceededException. يتوقع مطوّرو Java ذلك؛ كسر الاتفاقية يُربك القارئ.
Error أو Throwable مباشرة. Error محجوز لأخطاء JVM (نفاد الذاكرة، تجاوز stack). توسيع Throwable مباشرة يتجاوز التمييز بين المحقق وغير المحقق ويكسر افتراضات اللغة. وسّع دائمًا Exception أو RuntimeException.
الخلاصة
كلاسات الاستثناءات المخصصة تجعل كودك موثِّقًا لنفسه وأسهل في الصيانة. وسّع Exception للأخطاء القابلة للتعافي في النطاق، ووسّع RuntimeException للأخطاء البرمجية. وفّر دائمًا مُنشئًا للرسالة ومُنشئًا للسبب. أضف حقولًا حين يحتاج المُستدعون إلى بيانات منظمة تتجاوز رسالة نصية. أبقِ التسلسل الهرمي مسطحًا — قاعدة جيدة الاسم وعدد محدود من الأنواع الفرعية المحددة هو الشكل المناسب لمعظم التطبيقات.