سؤال

إخلاء المسئولية: أنا layperson تعلم البرنامج حاليا. لم تكن أبدا جزءا من مشروع، ولا كتب أي شيء أطول من 500 خطوط.

سؤالي هو: هل تنتهك البرمجة الدفاعية أن تكرر نفسك مبدأ؟ على افتراض تعريف برمجة دفاعية صحيحة (وجود وظيفة الاتصال التحقق من صحة المدخلات بدلا من العكس)، فلن يكون ذلك ضارا بالقمام الخاص بك؟

على سبيل المثال، هل هذا سيء:

int foo(int bar)
{
    if (bar != /*condition*/)
    {
        //code, assert, return, etc.
    }
}

int main()
{
    int input = 10;
    foo(input); //doesn't the extra logic
    foo(input); //and potentially extra calls
    foo(input); //work against you?
}   

مقارنة بهذا:

int main()
{
    if (input == /*condition*/)
    {
        foo(input);
        foo(input);
        foo(input);
    }
}

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

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

المحلول

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

المدخلات - وأني أقول أساسا المعلمات إلى الوظائف - يجب التحقق من التنفيذ كقاعدة عامة.

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

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

class A {
  public:
    const char *get_stuff();
}

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

A a = ...
char buf[1000];
strcpy(buf, a.get_stuff());

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

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

من ظروف بالطبع يمكن أن تغير هذا.

نصائح أخرى

تنتهك المبدأ الجاف يبدو أن هذا:

int foo(int bar)
{
    if (bar != /*condition*/)
    {
        //code, assert, return, etc.
    }
}

int main()
{
    int input = 10;
    if (input == /*condition*/)
    {
       foo(input);
       foo(input);
       foo(input);
    }
}

كما ترون، فإن المشكلة هي أن لدينا نفس التحقق مرتين في البرنامج، لذلك إذا تغير الحالة، يجب علينا تعديلها في مكانين، والاحتمالات التي نستها نحن ننسى أحدهم، مما يسبب سلوكا غريبا. يجف لا يعني "لا تنفذ نفس الرمز مرتين"، ولكن "لا تكتب نفس الرمز مرتين"

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

مثال على ذلك: أفعل الاختبارات المزدوجة في المراحل المهمة (مثل تسجيل الدخول تسجيل الدخول أولا - أول التحقق من صحة مرة واحدة قبل استدعاء تسجيل الدخول loginservice.login، ثم مرة أخرى في الداخل)، ولكن في بعض الأحيان أميل إلى إزالة الخارج الخارجي مرة أخرى بعد ذلك بعد أن كنت تتأكد من أن كل شيء يعمل بنسبة 100٪ ، عادة استخدام اختبارات الوحدة. هذا يعتمد.

لن أشعر بالعمل على الشيكات ذات الشرط المزدوج. نسيانها تماما من ناحية أخرى عادة ما تكون أحجام متعددة أسوأ :)

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

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

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

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

في مثالتك المبسطة، نعم، ربما يكون التنسيق الثاني هو الأفضل.

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

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

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

إذا كان لديك حقا نمط حيث "FOO" سيحصل على عدة مرات مع نفس الإدخال، أقترح وظيفة التفاف التي تقوم بالتحقق من الصحة مرة واحدة، وإصدار غير محمي من خلال التحقق من الصحة:

void RepeatFoo(int bar, int repeatCount)
{
   /* Validate bar */
   if (bar != /*condition*/)
   {
       //code, assert, return, etc.
   }

   for(int i=0; i<repeatCount; ++i)
   {
       UnprotectedFoo(bar);
   }
}

void UnprotectedFoo(int bar)
{
    /* Note: no validation */

    /* do something with bar */
}

void Foo(int bar)
{
   /* Validate bar */
   /* either do the work, or call UnprotectedFoo */
}

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

في أماكن أخرى، لا تحتاج كل ذلك.

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

إذا كان يجب التحقق من الإدخال دائما ما عليك سوى تضمينه في الوظيفة.

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