معالجة الاستثناء:العقد مقابل النهج الاستثنائي

StackOverflow https://stackoverflow.com/questions/26383

  •  09-06-2019
  •  | 
  •  

سؤال

أعرف طريقتين للتعامل مع الاستثناءات، فلنلقي نظرة عليهما.

  1. نهج العقد.

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

  2. نهج استثنائي.

    لا تضع استثناءات إلا عندما يحدث شيء غريب حقًا.يجب ألا تستخدم الاستثناءات عندما يمكنك حل الموقف من خلال تدفق التحكم العادي (عبارات If).لا تستخدم الاستثناءات للتحكم في التدفق، كما تفعل في نهج العقد.

يتيح استخدام كلا النهجين في حالات مختلفة:

لدينا فئة العملاء التي لديها طريقة تسمى OrderProduct.

نهج العقد:

class Customer
{
     public void OrderProduct(Product product)
     {
           if((m_credit - product.Price) < 0)
                  throw new NoCreditException("Not enough credit!");
           // do stuff 
     }
}

النهج الاستثنائي:

class Customer
{
     public bool OrderProduct(Product product)
     {
          if((m_credit - product.Price) < 0)
                   return false;
          // do stuff
          return true;
     }
}

if !(customer.OrderProduct(product))
            Console.WriteLine("Not enough credit!");
else
   // go on with your life

وهنا أفضل النهج الاستثنائي، لأنه ليس من الاستثنائي حقًا أن لا يكون لدى العميل مال على افتراض أنه لم يفز باليانصيب.

ولكن هنا موقف أخطأت فيه بشأن أسلوب العقد.

استثنائي:

class CarController
{
     // returns null if car creation failed.
     public Car CreateCar(string model)
     {
         // something went wrong, wrong model
         return null;
     }
 }

عندما أستدعي طريقة تسمى CreateCar، أتوقع ظهور مثيل Car بدلاً من مؤشر فارغ رديء، والذي يمكن أن يدمر كود التشغيل الخاص بي بعد عشرات الأسطر.لذلك أفضّل العقد على هذا:

class CarController
{

     public Car CreateCar(string model)
     {
         // something went wrong, wrong model
         throw new CarModelNotKnownException("Model unkown");

         return new Car();
     }
 }

ما هو النمط الذي تستخدمه؟ما رأيك هو أفضل نهج عام للاستثناءات؟

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

المحلول

أنا أؤيد ما تسميه نهج "العقد".إن إرجاع القيم الخالية أو القيم الخاصة الأخرى للإشارة إلى الأخطاء ليس ضروريًا في اللغة التي تدعم الاستثناءات.أجد أنه من الأسهل بكثير فهم التعليمات البرمجية عندما لا تحتوي على مجموعة من عبارات "if (result == NULL)" أو "if (result == -1)" الممزوجة بما يمكن أن يكون منطقًا بسيطًا ومباشرًا للغاية.

نصائح أخرى

أسلوبي المعتاد هو استخدام العقد للتعامل مع أي نوع من الأخطاء بسبب استدعاء "العميل"، أي بسبب خطأ خارجي (أي ArgumentNullException).

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

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

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

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

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

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

TValue GetValue(TKey Key);
bool TryGetValue(TKey Key, ref TValue value);

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

 // In case of failure, set ok false and return default<TValue>.
TValue TryGetResult(ref bool ok, TParam param);
// In case of failure, indicate particular problem in GetKeyErrorInfo
// and return default<TValue>.
TValue TryGetResult(ref GetKeyErrorInfo errorInfo, ref TParam param);

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

  var myThingResult = myThing.TryGetSomeValue(ref ok, whatever);
  if (ok) { do_whatever }

ليس هذا هو النهج القياسي تمامًا، ولكن في بعض الحالات قد تبرر المزايا ذلك.

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