الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

محددات الأكواد Code Contracts (جديد دوت نت 4)

بدأه عبد العظيم بخاري في 12 سبتمبر 2010 · 2 رد · 626 مشاهدة · في Microsoft Visual C#.NET
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع
dd491992.codecontracts_project(en-us).png

جاءت الدوت نت 4 بكلاسات جديدة ضمن namespace تدعى System.Diagnostics.Contracts .

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

التصميم باستخدام المحددات والعقود هو فكرة مقتبسة عن اللغة البرمجية Eiffel وقد وضعتها مايكروسوفت في الإصدار الأحدث من الدوت نت 4 ضمن الnamespace التي ذكرناها System.Diagonstics.Contract والتي يمكن استعمالها في جميع لغات .NET.

تمكنك هذه الكلاسات من تعريف شروط ومحددات مسبقة ولاحقة ومحددات ثابتة invariants داخل ميثود ما .

تمثل المحددات السابقة عادة متطلبات الباراميترز اما المحددات اللاحقة فتمثل البيانات المرجعة returned data , فيما يتعلق بالمحددات الثابتة فهي التي تعرف متطلبات المتغيرات ضمن الميثود نفسها .

يمكن لمعلومات المحددات أن تترجم في كودات الdebug والrelease ويمكن ايضاً تعريف اسمبلي خاصة بالمحددات ويمكن عمل فحوصات للمحددات بشكل ستاتيكي دون الحاجة لتشغيل التطبيق .

يمكن ايضاً تعريف محددات للinterfaces والتي تفيد في تحديد كيفية عمل implementation للانترفيس.

يمكن لأدوات المحددات اعادة كتابة الأسمبلي لتدخل فحوصات المحددات داخل الكودات في وقت التشغيل runtime أو اثناء وقت الترجمة compile time وتضيف معلومات المحددات إلى توثيقات XML المنشئة.

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

vs2010_codecontracts.png

وضع خيار Perform Runtime Contract Checking لFull سوف يعرف الرمز CONTRACTS_FULL ولأن عديد من دوال المحددات مذيلة بالattribute المسمى [Conditional( “ CONTRACTS_FULL “ )] فجميع فحوصات وقت التشغيل سوف تتم بهذا الخيار .

ملاحظة 1 : حتى تعمل مع محددات الأكواد يمكنك استعمال الكلاسات المتوفرة في .NET 4 ضمن System.Diagnostics.Contracts لكن ليس هناك اي اداة موجودة في فيجوال ستديو 2010 مخصصة لهذا الغرض لذلك يجب أن تحمل اضافة للفيجوال ستديو من Microsoft DevLabs على الرابط الاتي :

http://msdn.microsoft.com/en-us/devlabs/dd491992.

طبعاً تحتاج ل Visual Studio Team System في حال اردت عمل static analysis بهذه الأداة اما فيما يتعلق بruntime analysis فاصدار Visual Studio Standard edition يكفي .

ملاحظة 2 : لاحظ أن الصورة السابقة بها منطقة بالوسط فيها خيارات Static Checking . هذه الخيارات متوفرة فقط في اصدار Visual Studio 2010 Premium فما فوق . هذه الخيارات تعمل على تفعيل فحص وجود اخطاء المحددات اثناء وقت التصميم للبرنامج دون الحاجة للانتظار لوقت التشغيل حتى تظهر تلك الأخطاء .

ملاحظة 3 : يجب أن تضيف الرفرنس Microsoft.Contracts للمشروع حتى تعمل معك هذه الأداة.

تعرف محددات الأكواد عن طريق كلاس Contract . جميع شروط المحددات سواء كانت سابقة أو لاحقة يجب وضعها في بداية الميثود ويمكنك ايضاً اضافة global event handler للContractFiled Event والتي تستدعى عندما لا يتم تطبيق شروط المحدد اثناء وقت التشغيل . فاستدعاء SetHandled() مع باراميتر ContractFiledEventArgs سوف يوقف النمط الإفتراضي الذي يظهر عادة عند حدوث استثناء exception .

Contract.ContractFailed += (sender, e) =>
{
Console.WriteLine(e.Message);
e.SetHandled();
};

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

الشروط المسبقة PreConditions

تفحص الشروط السابقة الباراميترز التي تمرر لميثود ما . الميثود Requires() و الميثود

Requires<TException>()

هما من الشروط السابقة التي يمكن تعريفها مع كلاس Contract .

يجب أن تعطي الميثود الأولى Requires قيمة منطقية Boolean ويمكنك اعطاءها باراميتر ثاني اختياري يحتوي على رسالة الخطأ التي تظهر في حال عدم تحقق الشرط .

يتطلب المثال التالي أن يكون الباراميتر min اقل أو يساوي الباراميتر max :

//انظر كودات Program.cs اخر الدرس
static void MinMax(int min, int max)
{
Contract.Requires(min <= max);
//...
}

المحدد التالي يرمي ArgumentNullException في حال كان الباراميتر o هو null , يجب أن تعلم أنه لن يتم رمي الإستثناء في حال كان هناك event handler قام بوضع معالجة ل ContracFailed event . ايضاً اذا تم وضع اعداد التصريح عن فشل المحدد فإنه يتم عمل Trace.Assert() من اجل ايقاف البرنامج بدلاً من رمي استثناء Exception .

static void Preconditions(object o)
{
Contract.Requires<ArgumentNullException>(o != null,
"Preconditions, o may not be null");
//...

الميثود Requires<TException>() ليست مذيلة بالattribute المدعو [Conditional(“CONTRACTS_FULL“)] , وايضاً لا تملك أي شرط على رمز DEBUG لذلك يتم هذا الفحص في وقت التشغيل في كل الأحوال .

هذه الميثود ايضاً ترمي استثناء معرف في حال لم يتحقق الشرط .

كنا عادة نفحص الarguments بجمل if الشرطية وبعدها يتم رمي استثناء Exception في حال عدم تحقق الشرط .

اما مع محددات الأكواد ليس هناك حاجة لاعادة كتابة التحقق ; فقط ضع سطر واحد .

static void PrecondtionsWithUsuallyCode(object o)
{
if (o == null) throw new ArgumentNullException("o");
Contract.EndContractBlock();

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

يقدم الكلاس Contract الميثود Exists() والميثود ForAll() من اجل فحص الCollections المستخدمة كباراميتر للميثود .

تفحص الميثود ForAll() كل عنصر في الCollection في حال تحقق الشرط ففي المثال التالي نفحص اذا كان كل عنصر في الcollection لديه قيمة اقل من 100 .

فيما يتعلق بالميثود Exists() فهي تفحص اذا كان هناك عنصر واحد على الأقل في الcollection يحقق الشرط.

static void ArrayTest(int[] data)
{
Contract.Requires(Contract.ForAll(data, i => i < 100));

يوجد للميثود Exists() والميثود ForAll() اشكال اخرى من الoverloads تمكنك من تمرير رقمي integer وهما fromInclusive و toEnclusive بدلاً من IEnumerable<T> .

يمكن ايضاً تمرير نطاق من الأرقام (باستثناء toExclusive) إلى الdelegate المدعو Predicate<int> المعرف في الباراميتر الثالث.

يمكنك استعمال Exists() و ForAll() مع الشروط المسبقة واللاحقة ومع الشروط الثابتة Invariants.

الشروط اللاحقة PostConditions

تقدم الشروط اللاحقة ضمانات حول البيانات المشتركة والقيم المرجعة return values ويجب كتابة هذه الشروط في بداية الميثود وليس في نهايتها .

من الشروط اللاحقة الميثود Ensures() والميثود

EnsuresOnThrow<TException>()

يحدد المثال التالي أن المتغير shreadAmount يجب أن يكون اقل من 5000 في نهاية الميثود مع العلم أن هذه القيمة يمكن تغييرها في الوسط.

//انظر كودات Program.cs اخر الدرس
private static int sharedAmount = 4000;
    static void Postcondition()
    {
      Contract.Ensures(sharedAmount < 5000);
      sharedAmount = 9000;
      Console.WriteLine("change sharedAmount invariant {0}", sharedAmount);
      sharedAmount = 3000;
      Console.WriteLine("before returning change it to a valid value {0}", sharedAmount);
    }

تضمن الميثود EnsuresOnThrow<TException>() أن الكمية المشتركة sharedAmount تحقق الشرط في حال تم رمي استثناء محدد.

حتى نضمن القيمة المرجعة return value يمكننا استعمال القيمة الخاصة

Result<T>

مع محدد Ensures() ولأننا هنا نملك قيمة int فيمكننا وضعها للميثود Result() لأن T هو نوع generic .

بعد كل هذا سوف يضمن لنا المحدد Ensures() أن القيمة المرجعة return value هي اقل من 5000.

static int ReturnValue()
    {
      Contract.Ensures(Contract.Result<int>() < 5000);
      return 3;
    }

يمكنك ايضاً مقارنة قيمة ما مع القيمة القديمة لها وهذا يتم مع الميثود

OldValue<T>()

والتي ترجع القيمة الأصلية لمدخلات الميثود من المتغيرات الممررة لها.

يتأكد المثال التالي أن المحدد يعرف أن الناتج المرجع

(Contract.Result<int>())

هو اكبرمن القيمة القديمة القادمة من الباراميتر x

(Contract.OldValue<int>(x))

كودات المثال :

static int ReturnLargerThanInput(int x)
{
Contract.Ensures(Contract.Result<int>() > Contract.OldValue<int>(x));
return x + 3;
}

في حال ارجعت الميثود قيم باستخدام المعيار out بدلاً من جملة return العادية فيمكن تعريف الشروط باستخدام الميثود ValueAtReturned() .

يوجد في المثال التالي محدد يعرف أن المتغير x يجب أن يكون أكبر من 25 واصغر من 50 عندما يتم ارجاعه .

وايضاً يجب أن يكون باقي قسمة المتغير y على 5 هو صفر عن الإرجاع .

static void OutParameters(out int x, out int y)
{
Contract.Ensures(Contract.ValueAtReturn<int>(out x) > 25 &&
Contract.ValueAtReturn<int>(out x) < 50);
Contract.Ensures(Contract.ValueAtReturn<int>(out y) % 5 == 0);
x = 30;
y = 10;
}

المحددات الثابتة Invariants

هذه المحددات تكون للمتغيرات اثناء تنفيذ الميثود . فكما تعلمنا سابقاً فالميثود Contract.Requires() تعرف الشروط السابقة والمتطلبات على القيم المدخلة للميثود (الباراميترز) وايضا رأينا كيف أن الميثود Contract.Ensures تعرف الشروط اللاحقة أي الشروط التي تكون على نهاية الميثود عندما يتم ارجاع قيم . هناك ايضاً ميثود Contract.Invariant() والذي يعرف الشروط التي يجب أن تحقق خلال زمن تنفيذ الميثود (وسطها).

//انظر كودات Program.cs اخر الدرس
static void Invariant(ref int x)
{
Contract.Invariant(x > 5);
x = 3;
Console.WriteLine("invariant value: {0}", x);
x = 9;
}

المحددات على الinterfaces

كما نعلم أنه في الinterfaces يمكننا تعريف عدة methods و properties و والتي يجب على الكلاس الذي يشتق هذا الإنترفيس أن يقوم بعمل implement لها كلها .

لكنك مع تعريف الإنترفيس فلا يمكنك تحديد الطريقة التي يجب للانترفيس أن يتم عمل implementation له . اما الان اصبح هذا ممكناً مع محددات الأكواد code contracts .

بالنظر للانترفيس التالي IStudent الذي يعرف عدة properties ك FirstName و LastName و Age ويعرف ايضاً الميثود ChangeName() . كل هذه الأمور نعرفها لكن الجديد هو الattribute الذي يظهر قبل الإنترفيس وهو Contract() .

هذا الattribute يطبق على الإنترفيس IStudent ويعرف أن كلاس StudentContract هو المستخدم لعمل محددات أكواد لهذا الإنترفيس :

//ملف IStudent.cs 
using System.Diagnostics.Contracts;[ContractClass(typeof(StudentContract))]
  public interface IStudent
  {
    string FirstName { get; set; }
    string LastName { get; set; }
    int Age { get; set; }
    void ChangeName(string firstName, string lastName);
  }

يقوم الكلاس StudentContract بعمل implement للانترفيس IStudent ويعرف محددات الاكواد لجميع الأعضاء .

PureAttribute تعني أن الميثود أو الproperty قد لا تغير حالة الinstance للكلاس . هذا يتم تعريفه داخل get للproperties لكن يمكن ايضاً تعريفه مع جميع الmethods التي لا يسمح بتغير الحالة لها state.

الget لكل من FirstName و LastName تعرف ايضاً أن الناتج يجب أن يكون string باستخدام Contract.Result() اما الget لل Age فتعرف أن الشرط اللاحق يجب أن يتأكد أن القيمة هي بين 0 و 120 .

الset لل FirstName و LastName تطلب أن تكون القيمة الممررة ليست null اما فيما يخص الset لل Age فتعرف أن الشرط السابق يجب أن يكون بين 0 و 120 .

//ملف StudentContract.cs 
using System;
using System.Diagnostics.Contracts;  [ContractClassFor(typeof(IStudent))]
  public sealed class StudentContract : IStudent
  {
    string IStudent.FirstName
    {
      [Pure]
      get { return Contract.Result<String>(); }
      set { Contract.Requires(value != null); }
    }
    string IStudent.LastName
    {
      [Pure]
      get { return Contract.Result<String>(); }
      set { Contract.Requires(value != null); }
    }
    int IStudent.Age
    {
      [Pure]
      get
      {
        Contract.Ensures(Contract.Result<int>() > 0 && Contract.Result<int>() < 121);
        return Contract.Result<int>();
      }
      set
      {
        Contract.Requires(value > 0 && value < 121);
      }
    }
    void IStudent.ChangeName(string firstName, string lastName)
    {
      Contract.Requires(firstName != null);
      Contract.Requires(lastName != null);
    }
  }

الان اي كلاس يعمل implement للانترفيس IStudent يجب أن يحقق كافة محددات الأكواد المطلوبة .

كلاس Student الموضع هو عبارة عن تطبيق بسيط للانترفيس IStudent حيث يحقق كافة شروط المحددات .

//ملف Student.cs 

  public class Student:IStudent
  {
    #region IStudent Members

    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }


    public void ChangeName(string firstName, string lastName)
    {
      this.FirstName = firstName;
      this.LastName = lastName;
    }

    #endregion
  }

عند استعمال الكلاس Student يجب أن تتحقق المحددات فمثلاً ليس من المسموح اعطاء قيمة null ل property

//انظر كودات Program.cs اخر الدرس
var p = new Student{ FirstName = "Abed", LastName = null }; // contract error

ايضاً ليس من المسوح اعطاء قيمة ليست بين (0-120) للAge.

var p = new Student{ FirstName = "Abed", LastName = "Bukhari" };
p.Age = 131; // contract error

لمزيد من المعلومات عن هذه الأداة اذهب ل Microsoft DevLabs على الرابط

http:// msdn.microsoft.com/devlabs

ملف Program.cs
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Diagnostics;

  class Program
  {
    static void Main()
    {
      Contract.ContractFailed += (sender, e) =>
      {
        Console.WriteLine(e.Message);
        // e.SetHandled();
      };

      // var p = new Student { FirstName = "Abed", LastName = null }; // contract error
      //var p = new Student { FirstName = "Abed", LastName = "Bukhari" };
      //p.Age = 131; // contract error


      //int x = 7;
      //Invariant(ref x);
      //Postcondition();

      //int[] data = { 3, 5, 7, 11 };
      //ArrayTest(data);

      // MinMax(7, 2);
      //Preconditions(null);
      //PrecondtionsWithUsuallyCode(null);


    }

    static int ReturnValue()
    {
      Contract.Ensures(Contract.Result<int>() < 5000);
      return 3;
    }

    static int ReturnLargerThanInput(int x)
    {
      Contract.Ensures(Contract.Result<int>() > Contract.OldValue<int>(x));
      return x + 3;
    }

    private static int sharedAmount = 4000;
    static void Postcondition()
    {
      Contract.Ensures(sharedAmount < 5000);
      sharedAmount = 9000;
      Console.WriteLine("change sharedAmount invariant {0}", sharedAmount);
      sharedAmount = 3000;
      Console.WriteLine("before returning change it to a valid value {0}", sharedAmount);
    }


    static void Invariant(ref int x)
    {
      Contract.Invariant(x > 5);
      x = 3;
      Console.WriteLine("invariant value: {0}", x);
      x = 9;
    }

    static void ArrayTest(int[] data)
    {
      Contract.Requires(Contract.ForAll(data, i => i < 100));


      // Contract.Requires(Contract.Exists(data, i => i < 5));
      Console.WriteLine("ArrayTest contract succeeded");
    }

    static void OutParameters(out int x, out int y)
    {
      Contract.Ensures(Contract.ValueAtReturn<int>(out x) > 25 && Contract.ValueAtReturn<int>(out x) < 50);
      Contract.Ensures(Contract.ValueAtReturn<int>(out y) % 5 == 0);
      x = 30;
      y = 10;
    }

    static void MinMax(int min, int max)
    {
      Contract.Requires(min <= max);
      Contract.Requires<ArgumentException>(min <= max);
    }

    static void Preconditions(object o)
    {
      Contract.Requires<ArgumentNullException>(o != null, "Preconditions, o may not be null");
      Console.WriteLine(o.GetType().Name);
    }

    static void Foo(object o)
    {
      Contract.Requires(o != null, "o is null!");
      Contract.Ensures(o != null);

      Console.WriteLine("Foo");
    }

    static void PrecondtionsWithUsuallyCode(object o)
    {
      if (o == null) throw new ArgumentNullException("o");
      Contract.EndContractBlock();
    }

  }

عبد العظيم بخاري

http://www.el-bukhari.com

تم تعديل هذه المشاركة بواسطة عبد العظيم بخاري في 12 سبتمبر 2010 في 02:29

3
#3

بارك الله فيك ، و جعله الله في ميزان حسناتك.

لا خيــــــــــر في كاتــــــــــم العلــــــــــم

سأل الممكن المستحيل ، قال أين تسكن ، فال أسكن في عقول العاجزين

مواضيع مشابهة