سؤال

لقد صادفت النوع التالي من التعليمات البرمجية عدة مرات، وأتساءل عما إذا كانت هذه ممارسة جيدة (من منظور الأداء) أم لا:

try
{
    ... // some code
}
catch (Exception ex)
{
    ... // Do something
    throw new CustomException(ex);
}

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

كيف يختلف هذا في الأداء عن الاثنين التاليين:

try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw ex;
}

أو

try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw;
}

بغض النظر عن أي حجج لأفضل الممارسات الوظيفية أو الترميزية، هل هناك أي فرق في الأداء بين الأساليب الثلاثة؟

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

المحلول

@ براد تاتيرو

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

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

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

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

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

نصائح أخرى

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

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

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

  1. ليس من الجيد أن ترفع كل استثناء ككائن استثناء لأنك تفقد (على الأرجح) معلومات قيمة
  2. ليس جيدًا ولا رفع كل أنواع الأشياء التي تلتقطها لأنك بذلك تفشل في إنشاء الواجهة

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

كملاحظة جانبية:

أنا شخصياً لا أحب استخدام الاستثناءات (التسلسل الهرمي للفئات المشتقة من فئة الاستثناء) لتنفيذ المنطق.كما في الحالة:

try {
        // something that will raise an exception almost half the time
} catch( InsufficientFunds e) {
        // Inform the customer is broke
} catch( UnknownAccount e ) {
        // Ask for a new account number
}

مثل ديفيد، أعتقد أن الأداء الثاني والثالث أفضل.ولكن هل سيكون أداء أي واحد من الثلاثة سيئًا بما يكفي لقضاء أي وقت في القلق بشأن ذلك؟أعتقد أن هناك مشاكل أكبر من الأداء تدعو للقلق.

توصي FxCop دائمًا بالطريقة الثالثة بدلاً من الثانية حتى لا يتم فقدان تتبع المكدس الأصلي.

يحرر:تمت إزالة الأشياء التي كانت خاطئة تمامًا وكان مايك لطيفًا بما يكفي للإشارة إليها.

لا تفعل:

try
{
    // some code
}
catch (Exception ex) { throw ex; }

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

بدلاً من ذلك قم بما يلي:

try
{
    // some code
}
catch (Exception ex) { throw; }

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

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

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

قد تفشل القراءة من القرص (FileIOException)، وقد يحاول المستخدم الوصول إليه من مكان غير مسموح به (SecurityException)، وقد يكون الملف تالفًا (XmlParseException)، وقد تكون البيانات بتنسيق خاطئ (DeserialisationException).

في هذه الحالة، من الأسهل على فئة الاستدعاء فهم كل هذا، كل هذه الاستثناءات تعيد طرح استثناء مخصص واحد (FileOperationException) مما يعني أن المتصل لا يحتاج إلى مراجع إلى System.IO أو System.Xml، ولكن لا يزال بإمكانه ذلك الوصول إلى الخطأ الذي حدث من خلال التعداد وأي معلومات مهمة.

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

public bool Load(string filepath)
{
  if (File.Exists(filepath)) //Avoid throwing by checking state
  {
    //Wrap anyways in case something changes between check and operation
    try { .... }
    catch (IOException ioFault) { .... }
    catch (OtherException otherFault) { .... }
    return true; //Inform caller of success
  }
  else { return false; } //Inform caller of failure due to state
}

تحتوي الرميات الموجودة في المثال الأول على عبء إنشاء كائن CustomException جديد.

ستؤدي إعادة الطرح في المثال الثاني إلى طرح استثناء من النوع "الاستثناء".

ستؤدي إعادة الرمي في المثال الثالث إلى طرح استثناء من نفس النوع الذي تم طرحه بواسطة "بعض التعليمات البرمجية".

لذا فإن المثالين الثاني والثالث يستخدمان موارد أقل.

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

لقد رأيت فقط متطلبات الأداء فيما يتعلق بالنجاح ولكن لم أر مطلقًا فيما يتعلق بالفشل.

من وجهة نظر الأداء البحت، أعتقد أن الحالة الثالثة هي الأكثر أداءً.يحتاج الاثنان الآخران إلى استخراج تتبع المكدس وإنشاء كائنات جديدة، وكلاهما قد يستغرق وقتًا طويلاً إلى حد ما.

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

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