الخطوات التالية

مقدمة إلى البرمجة وC++

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

التعلم من خلال المثال رقم 2

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

المثال الأول: المزيد من التدريب على التحليل

ضع في الاعتبار المخرجات التالية من لعبة بسيطة:

Welcome to Artillery.
You are in the middle of a war and being charged by thousands of enemies.
You have one cannon, which you can shoot at any angle.
You only have 10 cannonballs for this target..
Let's begin...

The enemy is 507 feet away!!!
What angle? 25<
You over shot by 445
What angle? 15
You over shot by 114
What angle? 10
You under shot by 82
What angle? 12
You under shot by 2
What angle? 12.01
You hit him!!!
It took you 4 shots.
You have killed 1 enemy.
I see another one, are you ready? (Y/N) n

You killed 1 of the enemy.

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

StartUp(); // This displays the introductory script.
killed = 0;
do {
  killed = Fire(); // Fire() contains the main loop of each round.
  cout << "I see another one, care to shoot again? (Y/N) " << endl;
  cin >> done;
} while (done != 'n');
cout << "You killed " << killed << " of the enemy." << endl;

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

In case you are a little rusty on physics, here are the calculations:

Velocity = 200.0; // initial velocity of 200 ft/sec Gravity = 32.2; // gravity for distance calculation // in_angle is the angle the player has entered, converted to radians. time_in_air = (2.0 * Velocity * sin(in_angle)) / Gravity; distance = round((Velocity * cos(in_angle)) * time_in_air);

بسبب الاستدعاءات إلى cos() وsin()، ستحتاج إلى تضمينMath.h. حاول كتابة هذا البرنامج، فهي ممارسة رائعة في تحليل المسائل ومراجعة جيدة لـ C++ الأساسي. تذكر تنفيذ مهمة واحدة فقط في كل دالة. هذا هو البرنامج الأكثر تعقيدًا الذي كتبناه حتى الآن، لذا قد يستغرق تنفيذه بعض الوقت.إليك الحلّ الذي نقدّمه. 

المثال 2: التدرّب باستخدام المؤشرات

هناك أربعة نقاط يجب أخذها في الاعتبار عند استخدام المؤشرات:
  1. المؤشرات هي متغيرات تحتفظ بعناوين الذاكرة. وأثناء تنفيذ البرنامج، يتم تخزين جميع المتغيرات في الذاكرة، وتكون كل المتغيّرات في عنوانها أو موقعه الفريد. المؤشر هو نوع خاص من المتغيّر يحتوي على عنوان ذاكرة بدلاً من قيمة بيانات. مثلما يتم تعديل البيانات عند استخدام متغيّر عادي، يتم تعديل قيمة العنوان المخزّنة في المؤشر عن طريق معالجة متغيّر المؤشر. وفي ما يلي مثال:
    int *intptr; // Declare a pointer that holds the address
                 // of a memory location that can store an integer.
                 // Note the use of * to indicate this is a pointer variable.
    
    intptr = new int; // Allocate memory for the integer.
    *intptr = 5; // Store 5 in the memory address stored in intptr.
          
  2. ونشير عادةً إلى أنّ المؤشر "يشير" إلى الموقع الجغرافي الذي يخزّنه (يُشار إليه باسم "pointee"). إذًا في المثال أعلاه، يشير intptr إلى نقطة التوجيه 5.

    لاحِظ استخدام عامل التشغيل "new" لتخصيص ذاكرة لعدد صحيح نقطة. يجب إجراء ذلك قبل محاولة الوصول إلى الشخص المعني.

    int *ptr; // Declare integer pointer.
    ptr = new int; // Allocate some memory for the integer.
    *ptr = 5; // Dereference to initialize the pointee.
    *ptr = *ptr + 1; // We are dereferencing ptr in order
                     // to add one to the value stored
                     // at the ptr address.
          

    يتم استخدام عامل التشغيل * لتمييزها في C. من أكثر الأخطاء شيوعًا التي يرتكبها مبرمجو C/C++ عند التعامل مع المؤشرات هو عدم إعداد جهاز التوجيه. قد يؤدي هذا أحيانًا إلى حدوث عطل في وقت التشغيل لأننا نصل إلى موقع في الذاكرة يحتوي على بيانات غير معروفة. وإذا حاولنا تعديل هذه البيانات، يمكن أن يؤدي ذلك إلى حدوث تلف بسيط في الذاكرة، ما يجعل عملية تتبُّعها أمرًا صعبًا. 

  3. تعيين المؤشر بين مؤشرين تجعلهما يشيران إلى نفس النقطة. إذًا، فإن التكليف y = x; تجعل y تشير إلى نفس النقطة التي يرمز إليها x. ولا يؤدي تعيين المؤشر إلى لمس الشخص المعني. يؤدي ذلك إلى تغيير مؤشر واحد فقط ليصبح الموقع نفسه الذي يظهر فيه مؤشر آخر. بعد تعيين المؤشر، "يتشارك" المؤشران نقطة واحدة. 
  4. void main() {
     int* x; // Allocate the pointers x and y
     int* y; // (but not the pointees).
    
     x = new int; // Allocate an int pointee and set x to point to it.
    
     *x = 42; // Dereference x and store 42 in its pointee
    
     *y = 13; // CRASH -- y does not have a pointee yet
    
     y = x; // Pointer assignment sets y to point to x's pointee
    
     *y = 13; // Dereference y to store 13 in its (shared) pointee
    }
      

إليك أثر هذا الرمز:

1- خصِّص المؤشرين س و ص. لا يؤدي تخصيص المؤشرات إلى تخصيص أي نقاط.
2. يمكنك تخصيص جهاز توجيه وضبط x للإشارة إليه.
3. عليك الإشارة إلى x لتخزين 42 في الأداة. هذا مثال أساسي على عملية الإشارة المرجعية. ابدأ عند x، واتّبِع السهم المتجه للأعلى للوصول إلى نقطة Wifi.
4. حاول الإشارة إلى y تخزين 13 في الشخص المعني. يتعطل هذا بسبب عدم وجود موجه، وبالتالي لم يتم تعيين أي شخص له.
5. عيِّن y = x بحيث تشير y إلى نقطة x. والآن، نقطتين س و ص إلى نفس النقطة، فهي "تشارك".
6. حاول الإشارة إلى y تخزين 13 في الشخص المعني. وقد نجحت هذه المرة، لأن المهمة السابقة منحتك أحد المدربين.

كما ترون، تعد الصور مفيدة جدًا في فهم استخدام المؤشر. إليك مثالاً آخر.

int my_int = 46; // Declare a normal integer variable.
                 // Set it to equal 46.

// Declare a pointer and make it point to the variable my_int
// by using the address-of operator.
int *my_pointer = &my_int;

cout << my_int << endl; // Displays 46.

*my_pointer = 107; // Derefence and modify the variable.

cout << my_int << endl; // Displays 107.
cout << *my_pointer << endl; // Also 107.

لاحظ في هذا المثال أننا لم نخصص ذاكرة مطلقًا باستخدام عامل التشغيل "new". أعلنّا عن متغير عدد صحيح عادي وعالجناه باستخدام المؤشرات.

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

int *ptr1; // Declare a pointer to int.
ptr1 = new int; // Reserve storage and point to it.

float *ptr2 = new float; // Do it all in one statement.

delete ptr1; // Free the storage.
delete ptr2;

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

// Passing parameters by reference.
#include <iostream>
using namespace std;

void Duplicate(int& a, int& b, int& c) {
  a *= 2;
  b *= 2;
  c *= 2;
}

int main() {
  int x = 1, y = 3, z = 7;
  Duplicate(x, y, z);
  // The following outputs: x=2, y=6, z=14.
  cout << "x="<< x << ", y="<< y << ", z="<< z;
  return 0;
}

إذا تركنا الوسيطات &'s في تعريف الدالة "نسخة طبق الأصل"، سنمرر المتغير "حسب القيمة"، بمعنى أنّه سيتم عمل نسخة من قيمة المتغير. تؤدي أي تغييرات يتم إجراؤها على المتغيّر في الدالة إلى تعديل النسخة. ولا تُعدِّل المتغير الأصلي.

عند تمرير متغيّر من خلال المرجع، لا نمرر نسخة من قيمته، بل نمرر عنوان المتغيّر إلى الدالة. أي تعديل نُجريه على المتغيّر المحلي يؤدي إلى تعديل المتغيّر الأصلي الذي تم تمريره. 

فإذا كنت مبرمجًا بلغة C، فهذه بداية جديدة. يمكننا تنفيذ الإجراء نفسه في لغة C من خلال الإشارة إلى Duplicate() باعتباره Duplicate(int *x)، وفي هذه الحالة يشير x إلى مؤشر إلى عدد صحيح، ثم يتم استدعاء Duplicate() مع الوسيطة &x (address-of x)، واستخدام إلغاء تحديد مرجع x داخل Duplicate() (انظر أدناه). في المقابل، توفّر لغة C++ طريقة أبسط لتمرير القيم إلى الدوال بالاستناد إلى المرجع، على الرغم من أنّ طريقة C++ لا تزال صالحة للقيام بذلك.

void Duplicate(int *a, int *b, int *c) {
  *a *= 2;
  *b *= 2;
  *c *= 2;
}

int main() {
  int x = 1, y = 3, z = 7;
  Duplicate(&x, &y, &z);
  // The following outputs: x=2, y=6, z=14.
  cout << "x=" << x << ", y=" << y << ", z=" << z;
  return 0;
}

ملاحظة: في ما يتعلّق بمراجع C++ ، لسنا بحاجة إلى تمرير عنوان المتغيّر، ولا نحتاج إلى الإشارة إلى المتغيّر داخل الدالة المطلوبة.

ماذا ناتج البرنامج التالي؟ ارسم صورة للذاكرة لفهمها.

void DoIt(int &foo, int goo);

int main() {
  int *foo, *goo;
  foo = new int;
  *foo = 1;
  goo = new int;
  *goo = 3;
  *foo = *goo + 3;
  foo = goo;
  *goo = 5;
  *foo = *goo + *foo;
  DoIt(*foo, *goo);
  cout << (*foo) << endl;
}

void DoIt(int &foo, int goo) {
  foo = goo + 3;
  goo = foo + 4;
  foo = goo + 3;
  goo = foo;
} 

قم بتشغيل البرنامج لمعرفة ما إذا حصلت على الإجابة الصحيحة.

المثال 3: تمرير القيم حسب المرجع

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

المثال الرابع: الفئات والعناصر

ضع في الاعتبار الفئة التالية:

// time.cpp, Maggie Johnson
// Description: A simple time class.

#include <iostream>
using namespace std;

class Time {
 private:
  int hours_;
  int minutes_;
  int seconds_;
 public:
  void set(int h, int m, int s) {hours_ = h; minutes_ = m; seconds_ = s; return;}
  void increment();
  void display();
};

void Time::increment() {
  seconds_++;
  minutes_ += seconds_/60;
  hours_ += minutes_/60;
  seconds_ %= 60;
  minutes_ %= 60;
  hours_ %= 24;
  return;
}

void Time::display() {
  cout << (hours_ % 12 ? hours_ % 12:12) << ':'
       << (minutes_ < 10 ? "0" :"") << minutes_ << ':'
       << (seconds_ < 10 ? "0" :"") << seconds_
       << (hours_ < 12 ? " AM" : " PM") << endl;
}

int main() {
  Time timer;
  timer.set(23,59,58);
  for (int i = 0; i < 5; i++) {
    timer.increment();
    timer.display();
    cout << endl;
  }
}

لاحظ أن متغيرات أعضاء الفئة تحتوي على شرطة سفلية لاحقة. يتم ذلك للتمييز بين المتغيرات المحلية ومتغيرات الفئة.

أضِف طريقة إنقاص إلى هذه الفئة. يمكنك الاطّلاع هنا على الحلّ الذي نقدّمه.

عجائب العلوم: علوم الكمبيوتر

تدريبات

كما هو الحال في الوحدة الأولى من هذه الدورة، لا نقدم حلولاً للتدريبات والمشروعات.

تذكَّر أنّ البرنامج مفيد...

... مجزأة منطقيًا إلى دوال تؤدي فيها أي دالة واحدة مهمة واحدة فقط.

... برنامجًا رئيسيًا مكتوبًا كمخطط تفصيلي لما سيفعله البرنامج.

... يحتوي على دوال وصفية وأسماء ثابتة ومتغيرات.

... يستخدم الثوابت لتجنب أي أرقام "سحرية" في البرنامج.

... على واجهة مستخدم سهلة الاستخدام.

تمارين الإحماء

  • التمرين الأول

    للعدد الصحيح 36 خاصية خاصة، وهو مربّع مثالي وهو أيضًا مجموع الأعداد الصحيحة من 1 إلى 8. الرقم التالي هو 1225 وهو 352، ومجموع الأعداد الصحيحة من 1 إلى 49. أوجِد العدد التالي الذي يمثّل مربعًا مثاليًا ومجموع سلسلة 1...n. وقد يكون الرقم التالي أكبر من 32767. يمكنك استخدام دوال المكتبة التي تعرفها (أو الصيغ الرياضية) لجعل برنامجك يعمل بشكل أسرع. من الممكن أيضًا كتابة هذا البرنامج باستخدام التكرارات الحلقية لتحديد ما إذا كان الرقم مربّعًا مثاليًا أو مجموعًا لسلسلة. (ملاحظة: قد يستغرق العثور على هذا الرقم بعض الوقت بناءً على جهازك وبرنامجك).

  • التمرين الثاني

    يحتاج متجر الكتب في جامعتك إلى مساعدتك في تقدير نشاطه التجاري للعام القادم. وقد أظهرت التجارب أنّ المبيعات تعتمد بشكل كبير على ما إذا كان الكتاب مطلوبًا في دورة تدريبية أو اختيارية فقط، وعلى ما إذا تم استخدامه في الصف من قبل أم لا. سيتم بيع كتاب مدرسي جديد مطلوب لنسبة% 90 من الطلاب المحتمَلين، ولكن إذا سبق وتم استخدام الكتاب الدراسي الجديد داخل الصفوف الدراسية، سيتم شراء% 65 فقط منهم. وبالمثل، سيشتري 40% من الطلاب المحتملين كتابًا دراسيًا جديدًا اختياريًا، لكن لن يشتري سوى% 20 إذا سبق وتم استخدامه في الصف. (يُرجى العِلم بأنّ كلمة "مستعملة" هنا لا تعني الكتب المستعملة).

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

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

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

    إليك نموذج الناتج:

    Please enter the book code: 1221
     single copy price: 69.95
     number on hand: 30
     prospective enrollment: 150
     1 for reqd/0 for optional: 1
     1 for new/0 for used: 0
    ***************************************************
    Book: 1221
    Price: $69.95
    Inventory: 30
    Enrollment: 150
    
    This book is required and used.
    ***************************************************
    Need to order: 67
    Total Cost: $4686.65
    ***************************************************
    
    Enter 1 to do another book, 0 to stop. 0
    ***************************************************
    Total for all orders: $4686.65
    Profit: $937.33
    ***************************************************

مشروع قاعدة البيانات

في هذا المشروع، ننشئ برنامج C++ كامل الوظائف ينفذ تطبيق قاعدة بيانات بسيطًا.

سيتيح لنا برنامجنا إدارة قاعدة بيانات للملحنين والمعلومات ذات الصلة بأعمالهم. تشمل ميزات البرنامج ما يلي:

  • القدرة على إضافة ملحّن جديد
  • القدرة على ترتيب محتوى الملحّن (أي الإشارة إلى مدى الإعجاب أو عدم الإعجاب بموسيقى الملحّن)
  • القدرة على عرض كل الملحنين في قاعدة البيانات
  • إمكانية عرض جميع الملحنين حسب الرتبة

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

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

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

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

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

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

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

في ما يلي الخطوات التي نجريها للقيام بتصميم OO:

  1. حدِّد الفئات وحدِّد بشكل عام الغرض الذي يخزّنه عنصر من كل فئة كبيانات وما يمكن أن يفعله الكائن.
  2. تحديد عناصر البيانات لكل فئة
  3. حدِّد إجراءات كل فئة وكيفية تنفيذ بعض الإجراءات لفئة معيّنة باستخدام إجراءات الفئات الأخرى ذات الصلة.

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

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

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

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

الآن بعد أن حددنا الفئات لتطبيق قاعدة بيانات المؤلف، تتمثّل الخطوة التالية في تحديد السمات والإجراءات للفئات. في التطبيقات الأكثر تعقيدًا، نستعين بالقلم الرصاص والورق أو بطاقات UML أو بطاقات UML أو OOD لتحديد التسلسل الهرمي للفئات وكيفية تفاعل العناصر.

في قاعدة بيانات المؤلفين، نحدد فئة Composer تحتوي على البيانات ذات الصلة التي نريد تخزينها في كل مؤلف. وتتضمّن أيضًا طُرقًا للتلاعب في الترتيب وعرض البيانات.

تحتاج فئة قاعدة البيانات إلى نوع من الهياكل للاحتفاظ بكائنات Composer. يجب أن نتمكّن من إضافة كائن Composer جديد إلى البنية، بالإضافة إلى استرداد كائن Composer محدّد. نريد أيضًا عرض جميع الكائنات إما بترتيب الدخول أو حسب الترتيب.

تنفّذ فئة واجهة المستخدم واجهة قائمة على القوائم مع معالِجات تستدعي الإجراءات في فئة قاعدة البيانات. 

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

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

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

// composer.h, Maggie Johnson
// Description: The class for a Composer record.
// The default ranking is 10 which is the lowest possible.
// Notice we use const in C++ instead of #define.
const int kDefaultRanking = 10;

class Composer {
 public:
  // Constructor
  Composer();
  // Here is the destructor which has the same name as the class
  // and is preceded by ~. It is called when an object is destroyed
  // either by deletion, or when the object is on the stack and
  // the method ends.
  ~Composer();

  // Accessors and Mutators
  void set_first_name(string in_first_name);
  string first_name();
  void set_last_name(string in_last_name);
  string last_name();
  void set_composer_yob(int in_composer_yob);
  int composer_yob();
  void set_composer_genre(string in_composer_genre);
  string composer_genre();
  void set_ranking(int in_ranking);
  int ranking();
  void set_fact(string in_fact);
  string fact();

  // Methods
  // This method increases a composer's rank by increment.
  void Promote(int increment);
  // This method decreases a composer's rank by decrement.
  void Demote(int decrement);
  // This method displays all the attributes of a composer.
  void Display();

 private:
  string first_name_;
  string last_name_;
  int composer_yob_; // year of birth
  string composer_genre_; // baroque, classical, romantic, etc.
  string fact_;
  int ranking_;
};

تكون فئة قاعدة البيانات مباشرة أيضًا.

// database.h, Maggie Johnson
// Description: Class for a database of Composer records.
#include  <iostream>
#include "Composer.h"

// Our database holds 100 composers, and no more.
const int kMaxComposers = 100;

class Database {
 public:
  Database();
  ~Database();

  // Add a new composer using operations in the Composer class.
  // For convenience, we return a reference (pointer) to the new record.
  Composer& AddComposer(string in_first_name, string in_last_name,
                        string in_genre, int in_yob, string in_fact);
  // Search for a composer based on last name. Return a reference to the
  // found record.
  Composer& GetComposer(string in_last_name);
  // Display all composers in the database.
  void DisplayAll();
  // Sort database records by rank and then display all.
  void DisplayByRank();

 private:
  // Store the individual records in an array.
  Composer composers_[kMaxComposers];
  // Track the next slot in the array to place a new record.
  int next_slot_;
};

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

ستلاحظ عندما تبدأ العمل على تنفيذ فئتَي Composer و"قاعدة البيانات" وأنّه من الأفضل الحصول على صف Composer منفصل. وعلى وجه الخصوص، يؤدي إجراء عمليات قصيرة منفصلة في كائن Composer إلى تبسيط تنفيذ طرق Display() في فئة قاعدة البيانات بشكل كبير.

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

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

Composer Database
---------------------------------------------
1) Add a new composer
2) Retrieve a composer's data
3) Promote/demote a composer's rank
4) List all composers
5) List all composers by rank
0) Quit

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

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

// test_composer.cpp, Maggie Johnson
//
// This program tests the Composer class.

#include <iostream>
#include "Composer.h"
using namespace std;

int main()
{
  cout << endl << "Testing the Composer class." << endl << endl;

  Composer composer;

  composer.set_first_name("Ludwig van");
  composer.set_last_name("Beethoven");
  composer.set_composer_yob(1770);
  composer.set_composer_genre("Romantic");
  composer.set_fact("Beethoven was completely deaf during the latter part of "
    "his life - he never heard a performance of his 9th symphony.");
  composer.Promote(2);
  composer.Demote(1);
  composer.Display();
}

نحن بحاجة إلى برنامج اختبار مماثل لفئة قاعدة البيانات.

// test_database.cpp, Maggie Johnson
//
// Description: Test driver for a database of Composer records.
#include <iostream>
#include "Database.h"
using namespace std;

int main() {
  Database myDB;

  // Remember that AddComposer returns a reference to the new record.
  Composer& comp1 = myDB.AddComposer("Ludwig van", "Beethoven", "Romantic", 1770,
    "Beethoven was completely deaf during the latter part of his life - he never "
    "heard a performance of his 9th symphony.");
  comp1.Promote(7);

  Composer& comp2 = myDB.AddComposer("Johann Sebastian", "Bach", "Baroque", 1685,
    "Bach had 20 children, several of whom became famous musicians as well.");
  comp2.Promote(5);

  Composer& comp3 = myDB.AddComposer("Wolfgang Amadeus", "Mozart", "Classical", 1756,
    "Mozart feared for his life during his last year - there is some evidence "
    "that he was poisoned.");
  comp3.Promote(2);

  cout << endl << "all Composers: " << endl << endl;
  myDB.DisplayAll();
}

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

اكتمل تصميم طلبنا الآن. الخطوة التالية هي تنفيذ ملفات .cpp للصفوف وواجهة المستخدم.للبدء، انسخ والصق رمز h .واختبِر رمز برنامج التشغيل المذكور أعلاه في الملفات، واجمعها.واستخدم برامج التشغيل لاختبار الصفوف الدراسية. بعد ذلك، نفِّذ الواجهة التالية:

Composer Database
---------------------------------------------
1) Add a new composer
2) Retrieve a composer's data
3) Promote/demote a composer's rank
4) List all composers
5) List all composers by rank
0) Quit

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

ملاحظة: يجب أن تتّبع جميع الرموز البرمجية اصطلاحات الترميز الموضّحة هنا لتسهيل الأمر عليك:

  • يبدأ كل برنامج نكتبه بتعليق في العنوان يعرض اسم المؤلف ومعلومات الاتصال به ووصفًا موجزًا وطريقة الاستخدام (إذا كان ذلك منطبقًا). تبدأ كل دالة/طريقة بتعليق على العملية والاستخدام.
  • ونضيف تعليقات توضيحية باستخدام جمل كاملة عندما لا يوثق الرمز نفسه، على سبيل المثال، إذا كانت المعالجة صعبة أو غير واضحة أو مثيرة للاهتمام أو مهمة.
  • استخدِم دائمًا الأسماء الوصفية: المتغيّرات هي كلمات بأحرف صغيرة مفصولة بفواصل _، كما في my_variable. تستخدم أسماء الدوال/الطرق الأحرف الكبيرة لوضع علامة على الكلمات، كما هو الحال في MyExciteFunction(). تبدأ الثوابت بالحرف k وتستخدم أحرفًا كبيرة لوضع علامات على الكلمات، كما في kDaysInWeek.
  • المسافة البادئة في مضاعفات اثنين. المستوى الأول هو مسافات، وإذا كانت هناك حاجة إلى مسافة بادئة إضافية، نستخدم أربع مسافات وستة مسافات وهكذا.

مرحبًا بك في العالم الحقيقي!

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

ملفات Makefiles

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

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

أنظمة إدارة الإعداد

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

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

تنشأ حالة أكثر خطورة عندما يسمح كمبيوتر التخزين بفتح كل من يوسف وسوزان على المستند في الوقت نفسه. إليك ما يمكن أن يحدث:

  1. يفتح يوسف المستند على جهاز الكمبيوتر ويعمل على القسم الخاص به.
  2. تفتح سوزان المستند على جهاز الكمبيوتر وتعمل في القسم المخصص لها.
  3. يكمل خالد التغييرات ويحفظ المستند على كمبيوتر التخزين.
  4. تكمل سميرة تغييراتها وتحفظ المستند على كمبيوتر التخزين.

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

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

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

تفيد أنظمة "مدير الحملة" بشكل خاص في التحكم في التغييرات التي يتم إجراؤها على البرامج. وتُعرف هذه الأنظمة باسم أنظمة إدارة إعداد البرامج (SCM). إذا فكرت في العدد الضخم من ملفات رموز المصدر الفردية ضمن مؤسسة هندسة برمجيات كبيرة والعدد الكبير من المهندسين الذين يجب عليهم إجراء تغييرات عليها، يكون من الواضح أن نظام SCM أمر بالغ الأهمية.

إدارة تهيئة البرامج

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

توفر جميع أنظمة SCM الميزات الأساسية التالية:

  • إدارة التزامن
  • تحديد الإصدارات
  • المزامنة

في ما يلي شرح مفصّل لكل ميزة من هذه الميزات.

إدارة التزامن

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

بالنظر إلى مثال بسيط في المجال الهندسي: لنفترض أنّنا نسمح للمهندسين بتعديل الملف نفسه في الوقت نفسه في مستودع مركزي للرمز المصدر. يحتاج كل من Client1 وعميل2 إلى إجراء تغييرات على ملف في الوقت نفسه:

  1. يفتح البرنامج 1 bar.cpp.
  2. يفتح البرنامج 2 bar.cpp.
  3. يقوم Client1 بتغيير الملف وحفظه.
  4. يغيّر Client2 الملف ويحفظه بدلاً من تغييرات Client1.

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

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

تحديد الإصدارات

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

المزامنة

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

التخريب

Subversion (SVN) هو نظام مفتوح المصدر للتحكم في الإصدار. ويتضمّن هذا الإصدار كل الميزات الموضّحة أعلاه.

تتبنى SVN منهجية بسيطة عند حدوث تعارض. ويحدث التعارض عندما يُجري مهندسان أو أكثر تغييرات مختلفة على منطقة قاعدة الرموز نفسها، ثم يرسل كلاهما التغييرات. ينبّه SVN المهندسين فقط بوجود تعارض، والأمر متروك للمهندسين لحله.

سنستخدم ميزة SVN خلال هذه الدورة التدريبية لمساعدتك في التعرّف على إدارة الإعدادات. هذه الأنظمة شائعة جدًا في المجال.

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

بعض مصطلحات SVN

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

الآن بعد أن تم تثبيت SVN، سنتولى تنفيذ بعض الأوامر الأساسية. أول ما يجب فعله هو إعداد مستودع في دليل محدّد. وفي ما يلي الأوامر:

$ svnadmin create /usr/local/svn/newrepos
$ svn import mytree file:///usr/local/svn/newrepos/project -m "Initial import"
Adding         mytree/foo.c
Adding         mytree/bar.c
Adding         mytree/subdir
Adding         mytree/subdir/foobar.h

Committed revision 1.

ينسخ الأمر import محتوى الدليل mytree إلى مشروع الدليل في المستودع. يمكننا إلقاء نظرة على الدليل في المستودع باستخدام الأمر list.

$ svn list file:///usr/local/svn/newrepos/project
bar.c
foo.c
subdir/

لا يؤدي الاستيراد إلى إنشاء نسخة صالحة. ولتنفيذ هذا الإجراء، عليك استخدام الأمر svn Checkout. يؤدّي هذا إلى إنشاء نسخة صالحة من شجرة الدليل. لنفعل ذلك الآن:

$ svn checkout file:///usr/local/svn/newrepos/project
A    foo.c
A    bar.c
A    subdir
A    subdir/foobar.h
…
Checked out revision 215.

بعد أن تصبح لديك نسخة صالحة، يمكنك إجراء تغييرات على الملفات والأدلة هناك. وتشبه النسخة التي تعمل عليها أي مجموعة أخرى من الملفات والأدلة، حيث يمكنك إضافة ملفات وأدلة جديدة أو تعديلها أو نقلها أو حذف النسخة التي تكون ظاهرة بالكامل. تجدر الإشارة إلى أنّه في حال نسخ الملفات ونقلها في نسخة العمل، من المهم استخدام نسخ svn ونقل svn بدلاً من أوامر نظام التشغيل. لإضافة ملف جديد، استخدِم الأمر svn add ولحذف ملف، استخدِم الأمر svn delete. إذا كان كل ما عليك فعله هو التعديل، ما عليك سوى فتح الملف باستخدام المحرر وتعديله.

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

$ svn list file:///usr/local/svn/repos
/trunk
/branches

لِنفترض أنّك أجريت جميع التغييرات المطلوبة على نسخة العمل وتريد مزامنتها مع المستودع. إذا كان هناك مهندسون آخرون يعملون في هذا المجال من المستودع، من المهم الحفاظ على حداثة نسخة العمل. يمكنك استخدام الأمر svn status لعرض التغييرات التي أجريتها.

A       subdir/new.h      # file is scheduled for addition
D       subdir/old.c        # file is scheduled for deletion
M       bar.c                  # the content in bar.c has local modifications

تجدر الإشارة إلى أنّ هناك العديد من العلامات على أمر الحالة للتحكُّم في هذا الإخراج. إذا كنت تريد عرض التغييرات المحدّدة في ملف مُعدَّل، استخدِم svn diff.

$ svn diff bar.c
Index: bar.c
===================================================================
--- bar.c	(revision 5)
+++ bar.c	(working copy)
## -1,18 +1,19 ##
+#include
+#include

 int main(void) {
-  int temp_var;
+ int new_var;
...

وأخيرًا، لتعديل النسخة الصالحة للعمل من المستودع، استخدِم الأمر svn update.

$ svn update
U  foo.c
U  bar.c
G  subdir/foobar.h
C  subdir/new.h
Updated to revision 2.

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

بالنسبة إلى كل ملف يحتوي على تعارض، تضع Subversion ثلاثة ملفات في نسخة العمل:

  • file.mine: هذا هو ملفك كما كان في النسخة المخصّصة للعمل قبل تعديل النسخة الصالحة للعمل.
  • file.rOLDREV: هذا هو الملف الذي سحبته من المستودع قبل إجراء التغييرات.
  • file.rNEWREV: هذا الملف هو الإصدار الحالي في المستودع.

يمكنك اتخاذ أحد الإجراءات الثلاثة لحل التعارض:

  • انتقِل إلى الملفات ونفِّذ عملية الدمج يدويًا.
  • انسخ أحد الملفات المؤقتة التي تم إنشاؤها من خلال SVN على نسخة النسخة الصالحة للاستخدام.
  • نفِّذ ميزة التراجع عن svn لحذف جميع التغييرات.

بعد حل التضارب، يمكنك إبلاغ SVN من خلال تنفيذ تم حل svn. يؤدي هذا الإجراء إلى إزالة الملفات المؤقتة الثلاثة ولن يعرض SVN الملف بعد ذلك في حالة تعارض.

آخر شيء تفعله هو إدراج نسختك النهائية في المستودع. يتم هذا الإجراء باستخدام الأمر svn Confirm. عند تنفيذ تغيير، يجب تقديم رسالة سجلّ تصف التغييرات التي أجريتها. يتم إرفاق رسالة السجلّ هذه بالنسخة السابقة التي تنشئها.

svn commit -m "Update files to include new headers."  

هناك المزيد من المعلومات التي يجب معرفتها عن ميزة SVN وكيف يمكنها دعم المشاريع الكبيرة لهندسة البرامج. تتوفر موارد شاملة على الويب، ما عليك سوى إجراء بحث على Google عن "Subversion".

للتدريب، أنشئ مستودعًا لنظام قاعدة بيانات Composer واستورد جميع ملفاتك. بعد ذلك، تأكَّد من أنّ النسخة صالحة للاستخدام واطّلِع على الأوامر الموضّحة أعلاه.

المراجع

كتاب التقلبات على الإنترنت

مقالة ويكيبيديا عن SVN

الموقع الإلكتروني الخاص بالتخريب

التطبيق: دراسة في علم التشريح

يمكنك الاطّلاع على eSkeletons من جامعة تكساس في أوستن.