سؤال

هل يجب علي التقاط الاستثناءات لأغراض التسجيل؟

public foo(..)
{
   try
   {
     ...
   } catch (Exception ex) {
     Logger.Error(ex);
     throw;
   }
}

إذا كان لدي هذا في كل طبقة من طبقاتي (DataAccess وBusiness وWebService) فهذا يعني أنه تم تسجيل الاستثناء عدة مرات.

هل من المنطقي القيام بذلك إذا كانت طبقاتي موجودة في مشاريع منفصلة وكانت الواجهات العامة فقط هي التي تحاول/تلتقطها؟لماذا؟ولم لا؟هل هناك نهج مختلف يمكنني استخدامه؟

هل كانت مفيدة؟

المحلول

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

نصائح أخرى

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

أفضل ممارساتي هي:

  1. فقط حاول/أمسك بالطرق العامة (بشكل عام؛من الواضح أنك إذا كنت تتعقب خطأً محددًا فسوف تتحقق منه هناك)
  2. قم فقط بتسجيل الدخول إلى طبقة واجهة المستخدم مباشرة قبل منع الخطأ وإعادة التوجيه إلى صفحة/نموذج خطأ.

القاعدة العامة هي أنك لا تحصل على استثناء إلا إذا كان بإمكانك فعل شيء حيال ذلك.لذا، في طبقة الأعمال أو البيانات، لن تتمكن من اكتشاف الاستثناء إلا في مثل هذا الموقف:

            try
            {
                this.Persist(trans);
            }
            catch(Exception ex)
            {
                trans.Rollback();
                throw ex;
            }

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

في طبقة واجهة المستخدم، يمكنك تنفيذ معالج استثناء شائع:

Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);

والذي يعالج بعد ذلك كافة الاستثناءات.قد يقوم بتسجيل الاستثناء ثم يعرض استجابة سهلة الاستخدام:

    static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
    {
        LogException(e.Exception);
    }
    static void LogException(Exception ex)
    {
        YYYExceptionHandling.HandleException(ex,
            YYYExceptionHandling.ExceptionPolicyType.YYY_Policy,
            YYYExceptionHandling.ExceptionPriority.Medium,
            "An error has occurred, please contact Administrator");
    } 

في كود واجهة المستخدم الفعلي، يمكنك التعرف على الاستثناءات الفردية إذا كنت ستفعل شيئًا مختلفًا - مثل عرض رسالة ودية مختلفة أو تعديل الشاشة، وما إلى ذلك.

وأيضًا، للتذكير، حاول دائمًا التعامل مع الأخطاء - على سبيل المثال القسمة على 0 - بدلاً من طرح استثناء.

إنها ممارسة جيدة ترجمة الاستثناءات.لا تقم بتسجيلهم فقط.إذا كنت تريد معرفة السبب المحدد لطرح استثناء، فقم بطرح استثناءات محددة:

public void connect() throws ConnectionException {
   try {
       File conf = new File("blabla");
       ...
   } catch (FileNotFoundException ex) {
       LOGGER.error("log message", ex);
       throw new ConnectionException("The configuration file was not found", ex);
   }
}

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

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

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

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

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

على سبيل المثال (كود جافا الزائف):

public void methodWithDynamicallyGeneratedSQL() throws SQLException {
    String sql = ...; // Generate some SQL
    try {
        ... // Try running the query
    }
    catch (SQLException ex) {
        // Don't bother to log the stack trace, that will
        // be printed when the exception is handled for real
        logger.error(ex.toString()+"For SQL: '"+sql+"'");
        throw ex;  // Handle the exception long after the SQL is gone
    }
}

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

إذا طُلب منك تسجيل جميع الاستثناءات، فهذه فكرة رائعة.ومع ذلك، فإن تسجيل كافة الاستثناءات دون سبب آخر ليس فكرة جيدة.

قد ترغب في تسجيل الدخول على أعلى مستوى، والذي عادةً ما يكون واجهة المستخدم الخاصة بك أو رمز خدمة الويب.التسجيل عدة مرات هو نوع من النفايات.أيضًا، تريد معرفة القصة بأكملها عندما تنظر إلى السجل.

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

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

طريقتي هي تسجيل الاستثناءات في المعالج فقط.المعالج "الحقيقي" إذا جاز التعبير.وإلا سيكون من الصعب جدًا قراءة السجل وسيكون الكود أقل تنظيماً.

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

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

public foo(..)
{
   try
   {
     ...
   }
   catch (NullReferenceException ex) {
     DoSmth(e);
   }
   catch (ArgumentExcetion ex) {
     DoSmth(e);
   }
   catch (Exception ex) {
     DoSmth(e);
   }
}

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

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

أستخدم هذا النمط في مستويات الأعمال للتطبيقات التي تستخدم خدمات الويب عن بعد أو ASMX.باستخدام WCF، يمكنك اعتراض وتسجيل استثناء باستخدام IErrorHandler المرفق بـ ChannelDispatcher (موضوع آخر تمامًا) - لذلك لا تحتاج إلى نمط المحاولة/الالتقاط/الرمي.

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

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

  catch (const Exception& ex) { ... }

هذا يعد البودكاست الإذاعي لهندسة البرمجيات مرجعًا جيدًا جدًا لأفضل الممارسات في معالجة الأخطاء.هناك في الواقع محاضرتين.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top