التأكيد (تطوير البرمجيات)

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

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

تفاصيل

يحتوي الكود التالي على تأكيدين، x > 0و x > 1، وهما صحيحان بالفعل في النقاط المشار إليها أثناء التنفيذ:

int x = 1 ; assert x > 0 ; x ++ ; assert x > 1 ;

يستطيع المبرمجون استخدام التأكيدات للمساعدة في تحديد البرامج والتحقق من صحتها. على سبيل المثال، يحدد الشرط المسبق - وهو تأكيد يوضع في بداية جزء من التعليمات البرمجية - مجموعة الحالات التي يتوقع المبرمج أن يتم تنفيذ التعليمات البرمجية في ظلها. أما الشرط اللاحق - الذي يوضع في النهاية - فيصف الحالة المتوقعة عند انتهاء التنفيذ. على سبيل المثال x > 0 { x++ } x > 1:

يستخدم المثال أعلاه طريقة تضمين التأكيدات التي استخدمها كار هوار في مقالته عام 1969. [ 1 ] لا يمكن استخدام هذه الطريقة في لغات البرمجة الشائعة حاليًا. مع ذلك، يمكن للمبرمجين تضمين تأكيدات غير مُدققة باستخدام خاصية التعليقات في لغة البرمجة الخاصة بهم. على سبيل المثال، في لغة C++ :

x = 5 ; x = x + 1 ; // {x > 1}

تساعد الأقواس المضمنة في التعليق على تمييز هذا الاستخدام للتعليق عن الاستخدامات الأخرى.

قد توفر المكتبات أيضًا ميزات التأكيد. على سبيل المثال، في لغة C باستخدام مكتبة glibc مع دعم C99:

#include <assert.h>int f ( void ) { int x = 5 ; x = x + 1 ; assert ( x > 1 ); }

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

يساعد استخدام التأكيدات المبرمج على تصميم البرنامج وتطويره والتفكير فيه.

الاستخدام

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

التأكيدات في التصميم بموجب العقد

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

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

على سبيل المثال، يوضح ما يلي تصميم البرمجيات باستخدام العقود في لغة C++ (باستخدام عقود C++26 ). [ 2 ] [ 3 ]

int f ( const int x ) pre ( x != 1 ) // تأكيد شرط مسبق post ( r : r == x && r != 2 ) // تأكيد شرط لاحق؛ r هو اسم كائن نتيجة f { contract_assert ( x != 3 ); // عبارة تأكيد return x ; }

تأكيدات للتحقق أثناء التشغيل

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

int total = countNumberOfUsers (); if ( total % 2 == 0 ) { // المجموع زوجي } else { // المجموع فردي وغير سالب assert total % 2 == 1 ; }

في لغة جافا ، %يُستخدم عامل باقي القسمة ( modulo )، وفي جافا، إذا كان المعامل الأول سالبًا، فقد تكون النتيجة سالبة أيضًا (على عكس باقي القسمة المستخدم في الرياضيات). هنا، افترض المبرمج أن باقي القسمة totalغير سالب، وبالتالي فإن باقي القسمة على 2 سيكون دائمًا 0 أو 1. يؤكد هذا الشرط هذا الافتراض صراحةً: إذا countNumberOfUsersأعاد عامل باقي القسمة قيمة سالبة، فقد يكون البرنامج به خطأ برمجي.

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

تُوضع التأكيدات أحيانًا في مواضع لا يُفترض أن يصل إليها التنفيذ. على سبيل المثال، يمكن وضع التأكيدات في defaultبند العبارة switchفي لغات مثل C و C++ و Java . أي حالة لا يعالجها المبرمج عمدًا ستؤدي إلى ظهور خطأ، وسيتوقف البرنامج بدلًا من الاستمرار بصمت في حالة خاطئة. في لغة D، يُضاف هذا التأكيد تلقائيًا عندما switchلا تحتوي العبارة على defaultبند.

في لغة جافا ، أصبحت التأكيدات جزءًا من اللغة منذ الإصدار 1.4. يؤدي فشل التأكيدات إلى ظهور استثناء AssertionErrorعند تشغيل البرنامج مع العلامات المناسبة، وبدونها يتم تجاهل عبارات التأكيد. في لغة سي ، تُضاف التأكيدات من خلال ملف الرأس القياسي الذي يُعرّفها كـ ماكرو يُشير إلى وجود خطأ في حالة الفشل، وعادةً ما يُنهي البرنامج. في لغة سي++ ، يوفر كل من ملفي الرأس و ماكرو التأكيد.<assert.h>assert(assertion)<assert.h><cassert>assert

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

تتيح بنى التأكيد في اللغة سهولة تطوير البرمجيات الموجهة بالاختبار (TDD) دون استخدام مكتبة خارجية.

في لغة C# ، لا يوجد ماكرو أو كلمة مفتاحية للتأكيد، ولكن بدلاً من ذلك توجد فئات System.Diagnostics.Debugتوفر System.Diagnostics.Traceطرقًا Assert().

في لغة Rust ، يوجد assert!()ماكرو.

التأكيدات خلال دورة التطوير

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

التأكيدات في بيئة الإنتاج

عند نشر برنامج في بيئة الإنتاج ، تُعطَّل عادةً عمليات التحقق لتجنب أي عبء إضافي أو آثار جانبية قد تنجم عنها. في بعض الحالات، تغيب عمليات التحقق تمامًا عن الكود المنشور، كما هو الحال في لغة C/C++ حيث تُستخدم عمليات التحقق عبر وحدات الماكرو. في حالات أخرى، مثل لغة Java، تكون عمليات التحقق موجودة في الكود المنشور ويمكن تفعيلها ميدانيًا لأغراض تصحيح الأخطاء. [ 4 ]

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

التأكيدات الثابتة

تُسمى التأكيدات التي يتم التحقق منها في وقت الترجمة بالتأكيدات الثابتة.

تُعدّ التأكيدات الثابتة مفيدةً للغاية في برمجة القوالب الوصفية أثناء الترجمة ، ولكن يمكن استخدامها أيضًا في لغات منخفضة المستوى مثل C عن طريق إدخال تعليمات برمجية غير صالحة إذا (وفقط إذا) فشل التأكيد. يدعم C11 و C++11 التأكيدات الثابتة مباشرةً static_assert. في الإصدارات السابقة من C، يمكن تنفيذ التأكيد الثابت، على سبيل المثال، كما يلي:

#define SASSERT(pred) switch(0){case 0:case pred:;}SASSERT ( شرط منطقي 

إذا (BOOLEAN CONDITION)كانت نتيجة الجزء خاطئة، فلن يتم تجميع الكود أعلاه لأن المُجمِّع لا يسمح باستخدام عبارتين منطقيتين بنفس القيمة الثابتة. يجب أن تكون القيمة المنطقية ثابتة وقت التجميع، على سبيل المثال، ستكون قيمة صحيحة في هذا السياق. لا يعمل هذا التركيب على مستوى الملف (أي ليس داخل دالة)، لذا يجب تضمينه داخل دالة.(sizeof(int)==4)

هناك طريقة أخرى شائعة [ 5 ] لتنفيذ التأكيدات في لغة C وهي:

static char const static_assertion [ ( BOOLEAN CONDITION ) ? 1 : -1 ] = { '!' };

إذا كانت (BOOLEAN CONDITION)نتيجة الجزء خاطئة، فلن يتم تجميع الكود أعلاه لأن المصفوفات لا يمكن أن يكون لها طول سالب. أما إذا كان المُجمِّع يسمح بطول سالب، فإن بايت التهيئة (الجزء '!') يجب أن يتسبب في ظهور خطأ حتى في المُجمِّعات المتساهلة للغاية. يجب أن يكون التعبير المنطقي قيمة ثابتة وقت التجميع، على سبيل المثال، (sizeof(int) == 4)سيكون تعبيرًا صالحًا في هذا السياق.

تتطلب كلتا الطريقتين طريقةً لإنشاء أسماء فريدة. تدعم المترجمات الحديثة __COUNTER__تعريفًا للمعالج المسبق يُسهّل إنشاء أسماء فريدة، وذلك بإرجاع أرقام متزايدة بشكل رتيب لكل وحدة ترجمة. [ 6 ]

توفر لغة D تأكيدات ثابتة من خلال استخدام static assert. [ 7 ]

تعطيل التأكيدات

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

يمكن لبعض اللغات، بما في ذلك C و YASS و C++ ، إزالة التأكيدات تمامًا في وقت الترجمة باستخدام المعالج المسبق .

وبالمثل، فإن تشغيل مترجم بايثون باستخدام " -O " (اختصارًا لـ "optimize") كوسيط سيؤدي إلى عدم إصدار مولد كود بايثون أي بايت كود للتأكيدات. [ 8 ]

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

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

مقارنة بمعالجة الأخطاء

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

لنأخذ المثال التالي لاستخدام التأكيد لمعالجة الخطأ:

int * ptr = ( int * ) malloc ( sizeof ( int ) * 10 ); assert ( ptr ); // استخدام ptr ...

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

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

لننظر إلى نسخة أخرى من المثال السابق:

int * ptr ; // تفشل العبارة أدناه إذا أعادت malloc() قيمة NULL، // ولكنها لا تُنفذ إطلاقًا عند التجميع باستخدام -NDEBUG! assert ( ptr = ( int * ) malloc ( sizeof ( int ) * 10 )); // استخدم ptr: لم تتم تهيئة ptr عند التجميع باستخدام -NDEBUG! ...

قد يبدو هذا أسلوبًا ذكيًا لتعيين قيمة الإرجاع والتحقق منها mallocفي خطوة واحدة، لكن استدعاء الدالة وتعيين القيمة هما نتيجة ثانوية لتقييم التعبير الذي يشكل الشرط. عند تمرير المعامل إلى المُصرّف، كما هو الحال عند اعتبار البرنامج خاليًا من الأخطاء وإصداره، تُحذف العبارة، وبالتالي لا يتم استدعاء الدالة، مما يجعل المتغير غير مُهيأ. قد يؤدي هذا إلى خطأ تجزئة الذاكرة أو خطأ مؤشر فارغ مشابه في مراحل لاحقة من تنفيذ البرنامج، مما يتسبب في أخطاء قد تكون متفرقة أو يصعب تتبعها. يستخدم المبرمجون أحيانًا تعريفًا مشابهًا VERIFY(X) للتخفيف من هذه المشكلة.ptrNULLmallocptrassertNDEBUGassert()malloc()ptr

قد تُصدر المترجمات الحديثة تحذيراً عند مواجهة الكود المذكور أعلاه. [ 9 ]

تاريخ

في عام 1947، وصف فون نيومان وغولدستين [10] في تقاريرهما عن تصميمهما لآلة IAS، خوارزميات تستخدم نسخة مبكرة من مخططات التدفق ، تضمنت تأكيدات : " قد يكون صحيحًا أنه كلما وصل C إلى نقطة معينة في مخطط التدفق، فإن متغيرًا واحدًا أو أكثر من المتغيرات المقيدة سيمتلك بالضرورة قيمًا محددة، أو خصائص معينة، أو يحقق خصائص معينة فيما بينها. علاوة على ذلك، يمكننا، عند هذه النقطة، الإشارة إلى صحة هذه القيود. لهذا السبب، سنشير إلى كل منطقة يتم فيها تأكيد صحة هذه القيود بمربع خاص، نسميه مربع التأكيد."

دعا آلان تورينج إلى استخدام أسلوب التأكيدات لإثبات صحة البرامج . في محاضرة بعنوان "التحقق من روتين كبير" ألقاها في كامبريدج بتاريخ 24 يونيو 1949، اقترح تورينج: "كيف يمكن التحقق من روتين كبير للتأكد من صحته؟ لكي لا يواجه المدقق مهمة صعبة، ينبغي على المبرمج وضع عدد من التأكيدات المحددة التي يمكن التحقق منها بشكل فردي، والتي من خلالها يمكن استنتاج صحة البرنامج بأكمله بسهولة". [ 11 ]

انظر أيضاً

مراجع

  1. هوار، سي. أ. ر. (أكتوبر 1969). "أساس بديهي لبرمجة الحاسوب" . اتصالات رابطة آلات الحوسبة . 12 (10): 576-580 . doi : 10.1145/363235.363259 . S2CID 207726175 . 
  2. ^ جوشوا بيرن، تيمور دوملر، أندريه كرزيميينسكي (13 فبراير 2025). "عقود C++" (PDF) . open-std.org . مجموعة العمل 22.{{cite web}}: صيانة CS1: أسماء متعددة: قائمة المؤلفين ( رابط )
  3. "تأكيدات العقد (منذ C++26)" . cppreference.com . cppreference . تم الاطلاع عليه في 9 نوفمبر 2025 .
  4. البرمجة باستخدام التأكيدات ، تفعيل التأكيدات وتعطيلها
  5. جون جاغر، تأكيدات وقت الترجمة في لغة C ، 1999.
  6. جنو، "سلسلة إصدارات GCC 4.3  - التغييرات والميزات الجديدة والإصلاحات"
  7. "التأكيدات الثابتة" . مرجع لغة D. مؤسسة لغة D. تم الاسترجاع في 16-03-2022 .
  8. وثائق بايثون الرسمية، عبارة التأكيد
  9. "خيارات التحذير (باستخدام مجموعة مترجمات GNU (GCC))" .
  10. غولدستين وفون نيومان. "تخطيط وتشفير المشكلات لجهاز حاسوب إلكتروني" مؤرشف في 12 نوفمبر 2018 في Wayback Machine . الجزء الثاني، المجلد الأول، 1 أبريل 1947، ص 12.
  11. آلان تورينج. فحص روتين كبير ، 1949؛ مقتبس في محاضرة جائزة تورينج لعام 1980 لكار هوار، "ملابس الإمبراطور القديمة".