علوم البرمجة

ما هي البرمجة الإجرائية Procedural Programming

يعتبر تعلم قواعد لغات البرمجة وكيفية النصوص البرمجية الخطوة الأولى التي ينبغي على المتعلمين الجدد القيام بها، وعلى الرّغم من أهميتها، فإن تعلم قواعد اللغة ومكوناتها هو شرطٌ لازم ولكنه غير كافي من أجل كتابة برامج بأعلى كفاءةٍ ممكنة، حيث يمكن فهم البرمجة دومًا على أنها شقين: الشق الأول هو النص Syntax والشق الثاني هو المعنى Semantics.

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

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

هذا المقال مخصص لتسليط الضوء على مفهوم البرمجة الإجرائية Procedural Programing وأهم خصائصها بغض النظر عن اللعة المستخدمة.

ما هي البرمجة الإجرائية Procedural Programming

تعتبر البرمجة الإجرائية طريقةً فرعية تتبع لنموذج البرمجة الحتمية Imperative Programming، وهذا يعني أن المبرمج يقوم بتزويد الآلة (الحاسوب، الهاتف الذكيّ، الجهاز الذكي، المتحكم الصغريّ…الخ) بالخطوات اللازمة والمحددة من أجل تنفيذ غاية البرنامج، وهذا يعني أن هذا النمط من البرمجة مشابهٌ جدًا لكيفية عمل المعالج نفسه: يقوم المعالج بتنفيذ التعليمات واحدةً تلو الأخرى لتنفيذ عمليةٍ ما، وهو مشابهٌ لما يتم في مجال البرمجة الإجرائية، إذ يتكون البرنامج من مجموعةٍ من “الإجرائيات Procedures” التي يؤدي استدعاؤها وتنفيذها لتحقيق غاية البرنامج ككل.

بهذا السياق، قد يُساء فهم كلمة “إجرائية Procedure” على أنها “تابع Function“، وهذا أمرٌ خاطئ: التوابع تقوم بتوليد خرج وتعيد قيمًا معينة بحسب الوسطاء التي يتم تمريرها لها، أي أنه بحسب الدخل الممر لتابع، سنحصل على خرجٍ معين. الإجرائية لا تمثل تابعًا لأنها قد لا تعيد ببساطة أي قيمة، فالهدف المطلوب منها هو تنفيذ مهمة محددة.

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

قد يبدو الشرح السابق فلسفيًا بعض الشيء، ولذلك فإنه من المفيد أخذ مثالٌ بسيط من إحدى اللغات التي تدعم نموذج البرمجة الإجرائية، والمقصود هنا لغة C. لنقل أننا نريد كتابة برنامجٍ بسيط يقوم بحساب مربع الأعداد الموجبة من 1 إلى 10 ومن ثم عرض القيم على الشاشة. يمكن تنفيذ مثل هكذا برنامج عبر استدعاء إجرائيةٍ بسيطة تتكون من حلقةٍ تكرارية تبدأ من 1 وتنتهي بعشرة وتقوم بحفظ مربع الأعداد ضمن مصفوفةٍ من 10 قيم. مثل هكذا برنامج يأخذ الشكل التالي في لغة سي C:

تنويه: بالنسبة للنص البرمجيّ التالي، فإن كافة الأسطر التي تبدأ بالرمز // أو المكتوبة ضمن كتلة محددة بالرمزين /**/ هي عبارة عن “تعليقات Comments” لا يتم ترجمتها وتفسيرها من قبل مترجم اللغة، وينحصر استخدامها لتوضيح النص البرمجيّ وجعله أكثر فهمًا للقراءة.

/*
 ============================================================================
 Program     : Calculator.c
 Author      : Mario Rahal
 Description : Calculate the square value of numbers between 1 and 10 
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
        // Procedure #1: Print welcome message on the screen 
	printf("*******************************\n");
	printf("*** SQUARE VALUE CALCULATOR ***\n");
	printf("*******************************\n");

	unsigned int LoopIndex;
	unsigned int SquareArray[10];
	unsigned int ArrayIndex = 0;

	// Procedure #2: Calculating Square values 
	for (LoopIndex = 1; LoopIndex <= 10; LoopIndex = LoopIndex + 1)
	{
		SquareArray[ArrayIndex] = LoopIndex * LoopIndex;
		ArrayIndex = ArrayIndex + 1;
	}

	// Procedure #3: Leave one line distance
	printf("\n");

	// Procedure #4: Values printing 
	for (LoopIndex = 0; LoopIndex < 10; LoopIndex = LoopIndex +1)
	{
		printf(">> Element[%d] is %d and has a square value = %d\n", LoopIndex, LoopIndex+1, SquareArray[LoopIndex]);
	}
	
	return EXIT_SUCCESS;
}

يتألف النص البرمجيّ السابق من عدة أقسام يؤدي تنفيذها إلى إنجاز المهمة المطلوبة وهي حساب مربع الأعداد من 1 إلى 10 ومن ثم طباعة القيم على الشاشة. في البداية هنالك تضمين لمكاتب Libraries أساسية نحتاجها لاستخدام توابع مضمنة Built-in Functions مثل تابع الطباعة على الشاشة printf. تضمين المكاتب يتم عبر استخدام الموّجه include# متبوعًا باسم المكتبة. القسم الثاني هو التابع الرئيسيّ ()main الذي يتضمن الكود المراد تنفيذه. داخل هذا التابع هنالك ثلاث إجرائيات أساسية:

  • إجرائية طباعة جملة ترحيبية على الشاشة باستخدام التابع printf
  • إجرائية حساب قيم مربع الأعداد من 1 إلى 10 باستخدام الحلقة التكرارية for
  • إجرائية طباعة مربع الأعداد على الشاشة بعد ترك فراغ قدره سطر واحد

عملية تنفيذ الكود البرمجيّ السابق تتم بشكلٍ تسلسليّ Sequential أي أن الحاسب سيفهم السطور البرمجية ويقوم بتنفيذها سطرًا تلو الآخر. بحالة الحلقة التكرارية for، فإن مضمون الحلقة سيتم تنفيذه عددًا من المرات يساوي شرط مؤشر الحلقة وهو المتحول LoopIndex، وبهذه الحالة فإن عدد مرات التكرار يساوي 10. بكل مرة، سيتم حساب قيمة مربع العدد عبر ضربه بنفسه ومن ثم حفظ القيمة ضمن مصفوفة تتضمن 10 عناصر. أخيرًا، تعمل الحلقة التكرارية الأخيرة على طباعة مربع الأعداد عبر استدعاء عناصر المصفوفة واحدًا تلو الآخر.

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

متى نستخدم البرمجة الإجرائية؟

تعتبر البرمجة الإجرائية أبسط طرق ومناهج البرمجة ومن المنصف القول أنها تمثل أول ما يتعمله المبرمجون الجدد، حيث تتضمن معظم لغات البرمجة بنى تحكم Control Structures تساعد المبرمجين على كتابة الإجرائيات المختلفة، مثل الحلقات التكرارية Repetitive Loops والبنى الشرطية Conditional Statements. مجددًا، فإن منطق البرمجة الإجرائية يعني كتابة مجموعة من التعليمات المحددة التي تؤدي لتنفيذ المهمة المطلوبة وبدون أن يكون هنالك أي ارتباط عضوي بين البيانات، المتحولات والإجرائيات المستخدمة. بهذه الصورة، فإن هذا النمط من البرمجة سيكون مناسبًا – بشكلٍ عام – للحالات التالية:

  • عندما يمكن وصف الوظيفة المطلوبة إنجازها باستخدام منطق البناء من الأسفل للأعلى Top-Down Approach، أي أن سير البيانات والمعطيات له اتجاهٌ واحد يتم وصفه عبر سلسلة من الإجرائيات والشروط التي يتم تنفيذها واحدة تلو الأخرى.
  • عندما تكون الوظيفة المطلوب إنجازها “ساكنة Static” أي أنها ستبقى ثابتة ومحددة طوال فترة الاستخدام بحيث لا يكون هنالك حاجة لإضافات متحولات، بيانات أو إجرائيات جديدة في مراحل لاحقة.
  • عندما لا يكون هنالك أهمية كبيرة لحماية البيانات الخاصة بالوظيفة المطلوبة، حيث لا توّفر البرمجة الإجرائية آلياتٍ فعالة لحماية البيانات بخلاف البرمجة غرضية التوجه.
  • عندما لا يكون هنالك حاجة لإعادة استخدام النص البرمجيّ. بسبب اعتماد هذه الطريقة على كتابة تعليماتٍ محددة وواضحة، فإنه سيكون من الصعب إعادة استخدام نفس النص البرمجيّ لإنجاز وظيفةٍ أخرى مشابهة، وغالبًا ما يضطر المبرمج بمثل هكذا حالات لكتابة برنامجٍ آخر.

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

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

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

ما هي اللغات الإجرائية؟

يتم توصيف أي لغة برمجة على أنها لغة إجرائية Procedural Language في حال كانت تتيح للمبرمج كتابة النص البرمجيّ اعتمادًا على منطق البرمجة الإجرائية، وهنالك بعض اللغات التي يتم توصيفها على أنها إجرائية بالكامل، أي لا تتيح خصائص البرمجة غرضية التوجه، وهنالك بعض اللغات التي تتيح استخدام نموذج البرمجة الإجرائية بالإضافة لنماذج أخرى مثل البرمجة غرضية التوجه، وتندرج هذه اللغات تحت تصنيف اللغات متعددة المناهج Multi-Paradigm Languages.

تُعتبر لغة “سي C” البرمجية من أشهر اللغات المعتمدة على منطق البرمجة الإجرائية والتي لا تزال مستخدمةً على نطاقٍ واسع حتى يومنا هذا، وبشكلٍ عام، فإن معظم لغات البرمجة عالية المستوى القديمة (مثل فورتران Fortran، كوبول COBOL) هي لغات إجرائية. من ناحيةٍ أخرى، يمكن استخدام اللغات غرضية التوجه من أجل كتابة برامج تعتمد على منطق البرمجة الإجرائية، وأشهر الأمثلة على ذلك هي لغات سي بلس بلس ++C وجافا Java.

كلمة أخيرة: التصميم البرمجيّ هو آلية تفكير

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

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

مقالات ذات صلة

زر الذهاب إلى الأعلى