تقديم التوكيد (Introduce Assertion)

من موسوعة حسوب

المشكلة

لكي يعمل جزء من الشيفرة البرمجية بشكل صحيح، يجب أن تتحقق بعض الشروط أو تكون القيم صحيحة.

الحل

استبدل هذه الافتراضات بنقاط تحقق خاصة بالتوكيد.

مثال

قبل إعادة التصميم

تعريف دالة تعيد قيمة عددية - ثمتِّل حد النفقات - مع افتراض تحقق أحد أمرين دون التأكد من هما:

في لغة Java:

double getExpenseLimit() {
  // يجب أن يكون إما حدًا للنفقات أو مشروعًا أساسيًا
  return (expenseLimit != NULL_EXPENSE) ?
    expenseLimit:
    primaryProject.getMemberExpenseLimit();
}

في لغة C#‎:

double GetExpenseLimit() 
{
  // يجب أن يكون إما حدًا للنفقات أو مشروعًا أساسيًا
  return (expenseLimit != NULL_EXPENSE) ?
    expenseLimit:
    primaryProject.GetMemberExpenseLimit();
}

في لغة PHP:

function getExpenseLimit() {
  // يجب أن يكون إما حدًا للنفقات أو مشروعًا أساسيًا
  return ($this->expenseLimit != NULL_EXPENSE) ?
    $this->expenseLimit:
    $this->primaryProject->getMemberExpenseLimit();
}

في لغة Python:

def getExpenseLimit(self):
    # يجب أن يكون إما حدًا للنفقات أو مشروعًا أساسيًا
    return self.expenseLimit if self.expenseLimit != NULL_EXPENSE else \
        self.primaryProject.getMemberExpenseLimit()

بعد إعادة التصميم

إضافة توكيد يتأكد من تحقق الافتراضات التي عُدَّت على أنَها محققة:

في لغة Java:

double getExpenseLimit() {
  Assert.isTrue(expenseLimit != NULL_EXPENSE || primaryProject != null);

  return (expenseLimit != NULL_EXPENSE) ?
    expenseLimit:
    primaryProject.getMemberExpenseLimit();
}

في لغة C#‎:

double GetExpenseLimit() 
{
  Assert.IsTrue(expenseLimit != NULL_EXPENSE || primaryProject != null);

  return (expenseLimit != NULL_EXPENSE) ?
    expenseLimit:
    primaryProject.GetMemberExpenseLimit();
}

في لغة PHP:

function getExpenseLimit() {
  assert($this->expenseLimit != NULL_EXPENSE || isset($this->primaryProject));

  return ($this->expenseLimit != NULL_EXPENSE) ?
    $this->expenseLimit:
    $this->primaryProject->getMemberExpenseLimit();
}

في لغة Python:

def getExpenseLimit(self):
    assert (self.expenseLimit != NULL_EXPENSE) or (self.primaryProject != None)

    return self.expenseLimit if (self.expenseLimit != NULL_EXPENSE) else \
        self.primaryProject.getMemberExpenseLimit();

لم إعادة التصميم؟

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

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

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

فوائد تطبيق الحل

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

مساوئ تطبيق الحل

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

آلية الحل

عندما ترى أن الشرط قد اُفتُرِض، أضِف توكيدًا على هذا الشرط من أجل التأكد.

يجب أن لا تغيير إضافة التوكيد من سلوك البرنامج.

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

مصادر