لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 11/22/20 في كل الموقع
-
الإصدار 1.0.0
18695 تنزيل
لا يخفى على أحد شهرة لغة SQL أو لغة الاستعلامات البنيوية (Structured Query Language) سواءً للمبرمجين أو الداخلين الجدد إلى عالم البرمجة وعلوم الحاسوب، فهي لغة برمجة متُخصِّصة في مجال واحد وهو معالجة وإدارة قواعد البيانات، وتعد اللغة القياسية لأنظمة إدارة قواعد البيانات (RDBMS)؛ وتُستخدم تعليمات وأوامر SQL -لمن لا يعرفها- لإجراء عمليات مباشرة على البيانات، مثل تخزينها في قاعدة بيانات، وجلبها منها والتعديل عليها بالإضافة إلى إنجاز مهام إدارية على قواعد البيانات من تأمين ونسخ احتياطي وإدارة للمستخدمين. ونظرًا لأهمية SQL سواءً للمبرمجين، وحتى لغير المبرمجين من العاملين في القطاعات التقنية أو للمهتمين بقواعد البيانات عمومًا، نضع بين يديك هذا الكتاب المبني على أحد أفضل الكتب الإنجليزية المتقدمة عن SQL، وهو كتاب "SQL Notes For Professionals" من موقع GoalKicker المبني بدوره على توثيق موقع StackOverflow وقد ساهم في إعداده عدد كبير من المساهمين على شبكة StackOverflow الشهيرة (إن أردت الاطلاع على قائمة المساهمين الكاملة، ارجع إلى قسم "Credits" في نهاية الكتاب الأصلي، SQL Notes For Professionals). يغطِّي هذا الكتاب المفاهيم الأساسية للغة SQL، مثل العمليات الأولية، وإدراج البيانات وحذفها واستخلاصها وتحديثها، وأنواع البيانات، وتصميم الجداول وتنفيذ الاستعلامات، إضافة إلى مفاهيم متقدمة، مثل المعارض views والدوال، وإدارة المستخدمين، وكيفية تأمين الشيفرة وغيرها من المواضيع. كما أنّ الكتاب غني بالأمثلة التطبيقية التي تشرح كل هذه المواضيع لترسيخ فهمها. هذا الكتاب ليس مثل غيره من الكتب والشروحات التي تشرح لغة SQL من البداية شرحًا مُبسَّطًا ومتسلسلًا وإنَّما يعتمد على مبدأ خير الكلام ما قل ودل في الشرح وترك الشيفرة تشرح نفسها بنفسها، فيحوي على كم كبير من الشيفرات والأمثلة العملية بالموازنة مع الشرح النظري ووُجِّه لمن يريد اتقان لغة SQL وصقل مهاراته فيها إذ سيساهم هذا الكتاب في رفع مستواك في لغة SQL وسيُملِّكك مهارات متقدمة في استعمال لغة SQL بالإضافة إلى بعض الخدع والالتفافات المتقدمة أيضًا. هذا الكتاب مرخص بموجب رخصة المشاع الإبداعي Creative Commons «نسب المُصنَّف - غير تجاري - الترخيص بالمثل 4.0». أنشئ العمل الأصلي من هذا الكتاب لأغراض تعليمية ولا يتبع إلى أي شركة أو مجموعة رسمية متعلقة بلغة SQL ولا حتى شبكة Stack Overflow، كما أن جميع العلامات التجارية المذكورة في هذا الكتاب تتبع إلى الشركات المالكة لها. يمكنك قراءة فصول الكتاب على شكل مقالات من هذه الصفحة، «المرجع المتقدم إلى لغة SQL»، أو مباشرة مما يلي: المقال الأول: مدخل إلى SQL المقال الثاني: جلب الاستعلامات عبر SELECT في SQL المقال الثالث: التجميع والترتيب في SQL المقال الرابع: تنفيذ تعليمات شرطية عبر CASE في SQL المقال الخامس: البحث والتنقيب والترشيح في SQL المقال السادس: الدمج بين الجداول في SQL المقال السابع: تحديث الجداول في SQL المقال الثامن: معالجة الأخطاء والتعديل على قواعد البيانات في SQL المقال التاسع: حذف الجداول وقواعد البيانات في SQL المقال العاشر: مواضيع متقدمة في SQL المقال الحادي عشر: دوال التعامل مع البيانات في SQL المقال الثاني عشر: دوال التعامل مع النصوص في SQL المقال الثالث عشر: التعبيرات الجدولية الشائعة Common Table Expressions المقال الرابع عشر: مواضيع متفرقة في SQL المقال الخامس عشر: الاستعلامات الفرعية والإجراءات في SQL المقال السادس عشر: تصميم الجداول وترتيب تنفيذ الاستعلامات ومعلومات المخطط في SQL المقال السابع عشر: تنظيم وتأمين شيفرات SQL1 نقطة -
ستتناول هذه السلسلة حول استخدام puppet لإدارة الخواديم، والّتي هي عبارة عن ثلاثة أجزاء، الأداة Puppet وما تُقدمه لمُدراء الخوادم من تسهيلات في إدارة وإعداد الخوادم، وكيفيّة كتابة الشيفرة الخاصّة بهذه الأداة، كما سيتمّ التعامل مع الأداة Foreman والتعرّف على ماتُقدمه في هذا الخصوص. تُركز السلسلة على التطبيق العمليّ، لذلك يُنصح بالتطبيق المُباشر للأفكار المَطروحة فذلك أفضل سبيلٍ لتثبيث المَعلومة واحتراف التعامل مع هذه الأداة. مقالات سلسلة "استخدام puppet لإدارة الخواديم : تنصيب Puppet لإدارة البنية التَّحتِيَّة للخواديمملفّات البيان (Manifests) والوحدات (Modules) في Puppetكيف تستخدم Foreman لإدارة نقاط Puppet على الخادوم Ubuntu 14.04 مُقدّمةتُقدم مَعامل Puppet أداةً لإدارة الإعدادات وتُطلق عليها Puppet، ومُهمتها مُساعدةُ مُدراء الأنظمة في أتمتت الإعداد المُسبق provisioning وإعداد وإدارة البِنْيَة التَّحْتِيَّة الخاصّة بالخوادم. يُقلل التخطيط المُسبق واستخدام أدوات إدارة الإعدادات مثل الأداة Puppet من الوقت المُستغرق في تكرار المَهام الأساسية، ويُساعد على التأكّد من أنّ الإعدادات مَضْبُوطَةً بشكل دقيق، وتقومُ على أساسٍ واحد فيما بينها في جميع الخوادم. سيتوفّر المَزيد مِن الوقت الذي من المُمكن استغلاله في تحسين جوانب أُخرى في إعداد مَنظومة العمل، بعد احتراف إدارة الخوادم باستخدام الأداة Puppet أو حتّى أي أداة أتمتة أُخرى. يتوفّر إصداران من الأداة Puppet، إصدار المشاريع الكبيرة والمؤسسات Puppet Enterprise، والمَفتوح المَصدر. تعمل الأداة Puppet على مُعظم توزيعات نظام التشغيل لينكس ومُختلف مِنصّات UNIX وأنظمة التشغيل Windows. سيتناول هذا الشرح كيفيّة تنصيب الإصدار مفتوح المَصدر من الأداة Puppet على نظام رئيسيّ Master ومُمثل/عميل Agent، حيثُ يتألّف هذا النظام أو التقسيم من خادمٍ مركزيّ Puppet Master يحتوي جميع الإعدادات والبيانات المطلوبة لاستكمال المنظومة الإدارية للخوادم، وباقي الخوادم هي عُمَلاء للجهاز المَركزيّ Puppet Agent، والتي تُدار من قِبَله. مُتَطَلَّبات بيئة العمليَتطلّب تطبيق وإتمام هذا الشرح صلاحيّة وصولٍ كامل root access على جميع الخوادم الّتي ستكون جزء من منظومة Puppet، كما يُطلب إنشاء خادم جديد بتوزيعة Ubuntu الإصدار 14.04 والذي سوف يُمثل النظام المَركزيّ للأداة Puppet. قبل الشروع بتنصيب الأداة Puppet يجب التأكّد من الأمور التّالية: نظام أسماء النطاقات DNS للشبكة الخاصّة: يجب إعداد خادم DNS لتحويل العناوين بالاتّجاهين، من الأسماء إلى IPs ومن IPs إلى الأسماء، ويَجب على كل خادم أنّ يَمتلك اسمًا فريدًا خاصًا به. يُمكن الرجوع إلى الشرح التّالي بعنوان: إعداد خادم الـ DNS في الشبكة الداخليّة لإعداد الخادم الداخليّ، ويَجب استخدام المَلفّ hosts لترجمة الأسماء إلى عنوانين IPs بشكلٍ يدويٍّ في حال عدم توفّر مُخدم DNS.مَنافِذُ مَفتوحة على الجدارِ الناريّ firewall: تعمل الأداة Puppet على المنفذ 8140، لذلك في حال وجود جدارٍ ناريٍّ يُقيد الوصول عبر هذا المنفذ فيُمكن الرجوع إلى الشرح الخاصّ بالأداة UFW لاستعراض الخطواط المَطلوبة للسماح بمرور الطلبات عَبْر هذا المَنفذ.سيتمّ العمل على الخوادم التّالية الآن، ولاحقًا سيُضاف إليها خادمٌ في الجزء الثّاني، وآخر في الثّالث. ستتمّ تنصيب الأداة Puppet على جميع الخوادم السابقة. بيئة العمل السابقة هي نفسها الّتي تمّ العمل بها في هذا الدّرس. إنشاء الخادوم المَركزيّ Puppet Master Serverسيتمُّ استخدام توزيعة أوبونتو الإصدار 14.04 لتَكوْن الخادم المَركزيّ، ويحمل الخادم الاسم “puppet”. يُمكن الرُجوع إلى القسم الخاصّ بـ صيانة سجلات DNS في حال عدم توفّر المَعرفة لفعل ذلك. في حقيقة الأمر كل ما يجب عمله هو إضافة سجلّين الأول من نوع "A" والثاني من نوع "PRT"، والسماح للعميل الجديد بعمل استعلامات دوريّة recursive queries، وأيضًا يجب التأكد من إعداد DNS لترجمة أسماء الخوادم من دون استخدام "اسم النطاق المُؤهل بالكامل" Fully Qualified Domain Name. استخدام الاسم "puppet" للجهاز المَركزيّ سوف يُبسّط إعداد خوادم العُملاء بشكلٍ نسبيٍّ، وذلك لأنّه الاسم الافتراضيّ الذي سوف تَستخدمه هذه العُملاء عند مُحاولتها الاتصال مع الخادم الرئيسيّ Puppet Master. تنصيب NTPيجب على الخادم الرئيسيّ/المَركزيّ puppet master الحفاظ على توقيتٍ دقيقٍ في ساعة النظام لتفادي أو تجنب المشاكل المُحتملة عند إصدار شهادات العُملاء، وذلك بصفته مُفوّض الشّهادة للعُملاء agent. حيثُ كما يبدو أنّ صلاحيّة هذه الشهادات قد تنتهي عند وجود تعارضٍ في الوقت، ومن أجل ذلك سوف نستخدم "بروتوكول توقيت الشبكة" Network Time Protocol والذي يُشار إليه بالاختصار NTP. من المُمكن عمل مُزامنة يدويّة ولمرّة واحدة باستخدام الأمر ntpdate sudo ntpdate pool.ntp.orgيقوم الأمر السابق بتحديث الوقت، ولكن من الضروريّ الاعتماد على الأداة ntpd من أجل المُزامنة التلقائيّة وذلك من شأنه أنّ يقلّل من زمن ظاهرة "انجراف الوقت" Clock drift. sudo apt-get update && sudo apt-get -y install ntpمن الشائع تحديث إعدادات بروتوكول NTP ليستخدم "pools zones" قريبةً جغرافيًا من خادم NTP، حيثُ من المُمكن الرجوع إلى مشروع NTP والبحث عن أقرب pool zone إلى مركز البيانات datacenter المُستخدم، وللتعديل سوف يتمّ تحرير الملف المُسمى ntp.conf بواسطة المُحرر Vi، والذي سوف يتمّ استخدامه طوال هذه السلسلة في التحرير. sudo vi /etc/ntp.confسوف يتمّ إضافة الخوادم التّالية في بداية مَلفّ الإعدادات: server 0.us.pool.ntp.org server 1.us.pool.ntp.org server 2.us.pool.ntp.org server 3.us.pool.ntp.orgيتمّ حِفظ الإعدادات السابقة إما بتنفيذ الأمر :wq، أي الحفظ والخروج، أو عبر الاختصار ZZ. والآن يجب إعادة تشغيل الخدمة ليتمّ إضافة الخوادم الجديدة. sudo service ntp restartتنصيب الخادوم المَركزيّ Install Puppet Masterيوجد طرقٌ عدّة لتنصيب Puppet Master الإصدار مفتوح المصدر، سوف يتمّ استخدام حزمةٍ خاصّة مُقدمة من Puppet Labs وتُسمى “puppetmaster-passenger”، والّتي تتضمّن Puppet master بالإضافة إلى خادم ويب مُجهزٍ بشكل مُسبق للعمل مُباشرةً (Passenger مع Apache)، وذلك من شأنه أنّ يُسهل عمليّة الإعداد مُقارنةً مع الحزمة الأساسيّة فقط puppetmaster. يقوم الأمر التّالي بتحميل الحزمة السابقة الذكر من موقع PuppetLabs في المَسار الرئيسيّ $HOME cd ~; wget https://apt.puppetlabs.com/puppetlabs-release-trusty.debولتنصيب الحزمة يتمّ تنفيذ الأمر التّالي: sudo dpkg -i puppetlabs-release-trusty.debومن ثمّ التحديث للحصول آخر الحزم المُتوفّرة. sudo apt-get updateومن ثمّ تنصيب حزمة puppetmaster-passenger. sudo apt-get install puppetmaster-passengerوبهذا يكون قد تمّ تنصيب: الخادم المَركزيّ Puppet masterخادم الويب Apacheالحِزم المَطلوبة لعملية الإعدادبسبب استخدام الحزمة الخاصّة (Apache مع Passenger) يتحكم خادم الويب Apache بالأداة Puppet master، بمعنى أنّ هذه الخدمة سوف تعمل عندما يعمل خادم الويب Apache، وتتوقف بتوقفه. قبل المُتابعة سوف يتمُ إيقاف Puppet master وذلك بإيقاف خدمة apache2: sudo service apache2 stopقفل الإصدار الحالييُسبب في بعض الأحيان الانتقال من إصدارٍ لإصدارٍ آخر إلى توقّف منظومة الأدة Puppet عن العمل بشكلٍ مُلائمٍ، لذلك من الجيّد الحفاظ على إصدار الأداة Puppet في كامل المَنظومة على إصدار مُوحّد. وفي حال التحديث، من الضروريّ تحديث النسخة المَركزيّة master قبل نسخة العُملاء agent، حيثُ أنّ النسخة المَركزيّة لاتستطيع إدارة نُسخ العُملاء مع إصدارٍ أحدث من إصدارها. لمعرفة الإصدار الحالي للأداة Puppet يُمكن تنفيذ الأمر التّالي: puppet help | tail -n 1الإصدار الحالي والذي تعتمد عليه هذه السلسلة هو Puppet v3.7.4. من المُمكن استخدام الأداة APT وميزة pin لقفل إصدار الأداة Puppet للتحديثات الجذرية major والبسيطة minor والسماح بالترقيعات patches -يُمكن الرُجوع إلى الإدارة الدلاليّة لنُسخ البرمجيات لمزيد من التفاصيل حول إدارة الإصدار- ولذلك سوف يتمّ إنشاء مَلفّ جديد في المسار الخاصّ بتفضيلات الأداة apt. sudo vi /etc/apt/preferences.d/00-puppet.prefكما سوف يتمّ قفل التحديثات لثلاث حزم وهي: puppet، puppet common، puppetmaster-passenger وذلك بإضافة السطور التالية إلى المَلف السابق. # /etc/apt/preferences.d/00-puppet.pref Package: puppet puppet-common puppetmaster-passenger Pin: version 3.7* Pin-Priority: 501 إعداد الأسماء والشهاداتتستخدم الأداة Puppet شهادات SSL لتوثيق الاتصال بين الخادم المَركزيّ والعُملاء، حيثُ يُمثل الجهاز المَركزيّ دور "مُفوّض الشهادة" certificate authority، ويجب على CA أنّ يُولد الشهادة الخاصّة به والّتي تُستخدم في توقيع طلبات شهادات العُملاء. التخلّص من الشهادات الحاليّةيجب حذف أيّة شهادات SSL حاليّة قد تمّ إنشاؤها أثناء تنصيب حزمة puppet لإعداد مُفوّض الشهادة بشكل مُلائم. المسار الافتراضيّ لشهادات SSL الخاصّة بالحزمة توجد في المسار التّالي: /var/lib/puppet/ssl sudo rm -rf /var/lib/puppet/sslإعداد الشهادةعند إنشاء شهادة الجهاز المَركزيّ، يجب إضافة أي اسم DNS من المُمكن أنّ تستخدمه العُملاء للاتصال مع الجهاز المَركزيّ puppet master، في المثال الحالي سوف يتمّ إضافة اسم الخادم “puppet”، و"اسم نطاق مؤهل بالكامل" “puppet.nyc2.example.com”، وذلك بتحرير مَلفّ الإعدادات التّالي: sudo vi /etc/puppet/puppet.confوالذي يحتوي على البيانات التّالية: [main] logdir=/var/log/puppet vardir=/var/lib/puppet ssldir=/var/lib/puppet/ssl rundir=/var/run/puppet factpath=$vardir/lib/facter templatedir=$confdir/templates [master] # These are needed when the puppetmaster is run by passenger # and can safely be removed if webrick is used. ssl_client_header = SSL_CLIENT_S_DN ssl_client_verify_header = SSL_CLIENT_VERIFYسيتمّ حذف السطر الخاصّ بالخيار templatedir بعد أنّ أصبح غير مُستخدمًا بعد الآن، ومن ثمّ إضافة السطرين التاليين إلى نهاية القسم [main]. certname = puppet dns_alt_names = puppet,puppet.nyc2.example.com من الضروريّ تخصيص اسم الشهادة certname بالاسم “puppet”، لأنّ إعدادات Apache/Passenger مُعدّة لأنّ يكون اسم الشهادة بهذا الاسم. في جميع الأحوال يُمكن تخصيص الاسم حسب الرغبة بالتعديل على مَلفّ الإعداد الخاصّ بـApache: /etc/apache2/sites-available/puppetmaster.confوبهذا يكون قد تمّ الانتهاء من الإعداد بشكل مُلائم بعد الحفظ والخروج. توليد شهادةٍ جديدةيتمّ إنشاء شهادة جديدة بتنفيذ الأمر التّالي: sudo puppet master --verbose --no-daemonizeالأمر السابق سوف يعرض معلومات تُشير إلى إنشاء الشهادات ومفاتيح SSL، كما يُشير السطر الأخير بدء عمل الأداة Notice: Starting Puppet master version 3.7.4 Info: Creating a new SSL key for ca Info: Creating a new SSL certificate request for ca Info: Certificate Request fingerprint (SHA256): EC:7D:ED:15:DE:E3:F1:49:1A:1B:9C:D8:04:F5:46:EF:B4:33:91:91:B6:5D:19:AC:21:D6:40:46:4A:50:5A:29 Notice: Signed certificate request for ca ... Notice: Signed certificate request for puppet Notice: Removing file Puppet::SSL::CertificateRequest puppet at '/var/lib/puppet/ssl/ca/requests/puppet.pem' Notice: Removing file Puppet::SSL::CertificateRequest puppet at '/var/lib/puppet/ssl/certificate_requests/puppet.pem' Notice: Starting Puppet master version 3.7.4 لمعرفة معلومات عن الشهادة التي تمّ إصدارها بالإمكان تنفيذ الأمر التّالي: sudo puppet cert list -allيعرض الأمر السابق قائمة بجميع الشهادات المُوقعة وطلبات الشهادة غير المُوقعة. حتّى الآن سوف يعرض الأمر السابق الشهادة الخاصّة بالخادم الرئيسيّ فقط، وذلك لأنّه لم يتمّ إضافة أي شهادات أخرى ليتمّ عرضها. + "puppet" (SHA256) A5:72:AA:19:AF:E8:D2:9A:E8:99:B9:F0:19:B6:0A:7D:05:0D:0D:F6:BA:CE:F5:09:6F:73:0F:0C:DE:24:11:84 (alt names: "DNS:puppet", "DNS:puppet.nyc2.example.com") إلى هنا تكون الخدمة الخاصّة بالجهاز المَركزيّ Puppet master service هي تقريبًا جاهزة للعمل. إعداد الجهاز المَركزيّ Puppet Masterيحتوي مَلفّ الإعداد الرئيسيّ الخاصّ بالأداة puppet المُسمى puppet.conf على ثلاثة أقسام: [main] و [master] و [agent]. كما هو واضح، يحتوي القسم الرئيسي “main” على الإعدادات العامّة أو الشاملة، أما القسم المُسمى “master” فيحتوي على الإعدادات الخاصّة بالخادم الرئيسي puppet master، ويحتوي القسم “agent” على الإعدادات الخاصّة بخوادم العُملاء. بغض النظر عن التعديلات السابقة الّتي تمّت على هذا المَلفّ، تعمل الإعدادات الافتراضيّة بشكل مُناسب في بيئة العمل البسيطة وغير المُعقدة. يُمكن الرجوع إلى التوثيق الرسمي لمزيد من التفاصيل حول مَلفّ puppet.conf. ولتحرير هذا المَلف يُمكن تنفيذ الأمر التّالي: sudo vi /etc/puppet/puppet.confمَلفّ البيان الرئيسيّ (Main Manifest File)تستخدم الأداة Puppet لغة مُحددة المجال DSL وهي اختصار domain-specific language، ومُهمّة هذه اللغة هي كتابة أوصاف أو تصوّرات مَلفّ الإعدادات الخاصّ بالنظام، وتُحفظ هذه الأوصاف في ملفات تُسمى “mainfests”، والّتي تحمل اللاحقة .pp. يتوضّع مَلفّ الـ manifest الرئيسيّ وهو الافتراضيّ في المسار /etc/puppet/manifests/site.pp. سوف يتمّ تناول أساسيات التعامل مع هذا النوع من الملفات فيما بعد، ولكن الآن سيتمّ استخدام مَلفّ خُلَّبي placeholder للمُتابعة: sudo touch /etc/puppet/manifests/site.ppتشغيل الخادوم المَركزيّ Puppet Masterأصبح من المُمكن الآن تشغيل Puppet master عبر تشغيل خدمة apache2: sudo service apache2 startيعمل الخادم الرئيسيّ Puppet master كما يجب، ولكن حتى الآن هو لا يُدير أيًا من العُملاء بعد. تنصيب خوادم العُملاء Puppet Agentيجب تنصيب أداة العميل الخاصة بالأداة Puppet على جميع الخوادم التي من المُفترض إدارتها من قبل الجهاز المَركزيّ Puppet master، في أغلب الحالات هذا يتضمّن كل الخوادم في البِنْيَة التَّحْتِيَّة، حيثُ كما تمّت الإشارة في المُقدمة أنّ الأداة Puppet مُتوافقة مع أغلب توزيعات لينوكس الشائعة، وبعض منصات يونكس وأنظمة التشغيل "ويندوز". بسبب الاختلاف في الإعداد بين الأنظمة سوف يتمّ التركيز على خوادم Ubuntu و Debian، أما التعليمات الخاصّة بالتنصيب على بقيّة المنصات فيُمكن الحصول عليها من التوثيق الرسميّ: Puppet Labs Docs ملاحظة: من المُهم جدًا إعداد جميع العُملاء لاستخدام DNS مُلائم قبل تنصيب نسخة العُملاء على الخوادم. التعامل مع خوادم Ubuntu / Debianتستخدم جميع خوادم العُملاء في أمثلة هذا الشرح توزيعة أوبونتو 14.04، وذلك باستخدام خادم افتراضيّ خاصّ VPS، والإعدادات التّالية سوف يتمّ تطبيقها على جميع هذه الخوادم وهي: host1، host2، ns1، ns2. سوف يتمّ في البداية تحميل الحزمة من موقع مَعامل Puppet كما حصل مع نسخة Puppet master. cd ~; wget https://apt.puppetlabs.com/puppetlabs-release-trusty.debتنصيب الحزمة: sudo dpkg -i puppetlabs-release-trusty.debتحديث قوائم الحزم المُتوفّرة: sudo apt-get updateتنصيب حزمة العُملاء puppet: sudo apt-get install puppetالأداة بشكل افتراضيّ تَكون غير مُفعلة، ولتغيير ذلك يجب تحديث مَلفّها الافتراضيّ: sudo vi /etc/default/puppetتغيير قيمة START إلى “yes”: START=yesقفل الإصدار الحاليسوف يتمّ استخدام ميزة pin الخاصة بالأداة apt لقفل الإصدار الحالي الخاصّ بـ Puppet agent: sudo vi /etc/apt/preferences.d/00-puppet.prefلقفل الإصدار الحالي سوف يتمّ إضافة السطور التّالية إلى المَلفّ السابق: # /etc/apt/preferences.d/00-puppet.pref Package: puppet puppet-common Pin: version 3.7* Pin-Priority: 501 إعداد نسخة العميل Puppet Agentقبل تشغيل نسخة العميل من الضروريّ تغيير بعض الإعدادات، وذلك بتحرير مَلفّ إعداد نسخة العميل puppet.conf، وهو نسخة مُطابقة لنسخة لملف الإعداد الأوليّ لنسخة Puppet master: sudo vi /etc/puppet/puppet.confسيتمّ حذف السطر الخاصّ بالخَيار templatedir كما تمّ سابقَا مع نسخة الخادم المَركزيّ، بالإضافة إلى ذلك سوف يتمّ حذف القسم المُسمى [master] وكافة السطور الّتي تخصّه. إنّ تمكّنت الخوادم من الاتصال بالخادم المَركزيّ Puppet master بالاسم “puppet”، فيُمكن للعُملاء استخدام هذا الاسم للاتصال بالخادم المَركزيّ، أما في حال عدم توفّر الجهاز المَركزيّ تحت الاسم “puppet” فيجب إضافة اسم المجال الكامل FQDN الخاصّ بالخادم المَركزيّ Puppet master، ومن المُستحسن عمل ذلك في جميع الأحوال. [agent] server = puppet.nyc2.example.comبعد الإعدادات السابقة أصبحت نسخة العميل من الأداة Puppet agent جاهزة للعمل، وذلك بتنفيذ الأمر التّالي: sudo service puppet startلن يُظهر تنفيذ الأمر السابق أيّة مُخرجات output في حال أنّ الإعدادات طُبقت بشكل صحيح. يتمّ عند التشغيل الأول للأداة Puppet agent توليد شهادة SSL وإرسال طلب توقيع signing request إلى الخادم المَركزيّ Puppet master، وبعد أنّ يُوقع الخادم المَركزيّ شهادة العميل سيكون الاتصال جاهزًا بينهما. تجدرُ الإشارة هنا أنّه في حال كان هذا العميل هو العميل الأول، فمن المُستحسن محاولة توقيع الشهادة على الخادم المَركزيّ Puppet master قبل إضافة باقي العُملاء، وعندما يتمّ التأكد من أنّ الأمور تجري على ما يُرام، عندها يُمكن إضافة باقي العُملاء من دون التخوف من وقوع مُشكلات مُحتملة مع باقي الخوادم. توقيع طلب العُملاء على الخادم المَركزيّ: تُرسل الأداة Puppet عند تشغيلها للمرّة الأولى على خادم العميل Puppet agent طلب توقيع شهادة إلى الخادم المَركزيّ Puppet master، ولن يتمكّن الخادم المَركزيّ من التحكم بالخادم العميل حتى يوقع على هذه الشهادة. سوف يتمّ توضيح هذه النقطة في الفقرة التّالية. عرض طلبات الشهادة الحاليةيتمّ تنفيذ الأمر التّالي على الجهاز المَركزيّ Puppet master ليَعرض جميع طلبات الشهادة غير المُوقعة: sudo puppet cert listسيُظهر الأمر السابق طلبًا وحيدًا حتّى الآن، وهو للعميل الذي تمّ إعداده سابقًا، وسيبدو بالشكل التّالي، حيثُ الاسم هو اسم المجال الكامل FQDN: "host1.nyc2.example.com" (SHA256) 5F:DB:E0:6D:8B:1F:C8:A3:04:4E:CD:48:6F:9F:D7:89:14:38:FB:5B:EF:2F:09:76:C5:13:A9:E0:4D:D2:C5:FCيجب الانتباه هنا إلى أنّ المُخرج السابق لا يحتوي على إشارة الزائد (+)، وذلك يُشير إلى أنّ هذه الشهادة غير مُوقعة بعد. توقيع الطلبيُستخدم الأمر puppet cert sign لتوقيع طلب شهادة، ويُضاف إلى هذا الأمر اسم الخادم الخاصّ بهذه الشهادة، على سبيل المثال لتوقيع طلب الخادم host1.nyc2.example.com، فسوف يتم تنفيذ الأمر التّالي: sudo puppet cert sign host1.nyc2.example.comالأمر السابق يَعرضُ المُخرَج التّالي، والذي يُشير إلى توقيع طلب الشهادة. Notice: Signed certificate request for host1.nyc2.example.com Notice: Removing file Puppet::SSL::CertificateRequest host1.nyc2.example.com at '/var/lib/puppet/ssl/ca/requests/host1.nyc2.example.com.pem'أصبح بإمكان الخادم المَركزيّ Puppet master بعد توقيع طلب الشهادة من الاتصال والتحكم بهذه النقطة node والتي تنتمي إليها هذه الشهادة المُوقعة. يُمكن استخدام الأمر التّالي لتوقيع جميع الطلبات الحالية وذلك باستخدام الخيار -all. sudo puppet cert sign --allإبطال الشهاداتيحتاج مُدير النظام في بعض الحالات إلى حذف أحد العُملاء من الخادم المَركزيّ، ربما لأسباب تتعلق بتحليل المشاكل، أو لأي سببٍ آخر، حيثُ يُمكن إبطال شهادة العميل من الخادم المَركزيّ باستخدام الخيار clean مع اسم الخادم، كما في المثال التّالي: sudo puppet cert clean hostnameاستعراض جميع الطلبات المُوقعةيُستخدم الأمر التّالي لاستعراض جميع الطلبات المُوقعة وغير المُوقعة sudo puppet cert list –allيَعرض الأمر السابق جميع الطلبات مع الانتباه أنّ الطلبات المُوقعة تُسبق بإشارة الزائد "ns2.nyc2.example.com" (SHA256) E4:F5:26:EB:B1:99:1F:9D:6C:B5:4B:BF:86:14:40:23:E0:50:3F:C1:45:D0:B5:F0:68:6E:B2:0F:41:C7:BA:76 + "host1.nyc2.example.com" (SHA256) 71:A2:D3:82:15:0D:80:20:D4:7E:E3:42:C2:35:87:83:79:2B:57:1D:D5:5A:EC:F6:8B:EE:51:69:53:EB:6B:A1 + "host2.nyc2.example.com" (SHA256) F4:79:A6:7C:27:0C:EA:8E:BC:31:66:FF:F2:01:AB:B1:35:7B:9F:5E:C8:C9:CE:82:EE:E8:2F:23:9F:0C:2B:ED + "puppet" (SHA256) 05:22:F7:65:64:CF:46:0E:09:2C:5D:FD:8C:AC:9B:31:17:2B:7B:05:93:D5:D1:01:52:72:E6:DF:84:A0:07:37 (alt names: "DNS:puppet", "DNS:puppet.nyc2.example.com") الخطوات الأولى مع الأداة Puppetبعد تجهيز بيئة العمل، سيتمّ التركيز في الفقرات التالية على كيفية عمل بعض المُهِمّات أو الواجبات الأساسية للخوادم وذلك باستخدام الأداة Puppet، وذلك في سبيل عرض الخطوط العريضة لهذه الأداة وآلية عملها. كيف تُجمّع الحقائقتجمع الأداة Puppet الحقائق حول جميع العُقد المُرتبطة بها، وذلك باستخدام أداةٍ تُسمى “facter”. تَجمع الأداة Facter جميع المعلومات المُفيدة في إعداد النظام بشكل افتراضيّ، مثل اسم نظام التشغيل، عنواين برتوكول الانترنت IP، ومفاتيح SSH، وغيرها من المعلومات. كما مِن المُمكن إضافة حقائق مُخصّصة حسب الحاجة لأداء بعض الإعدادات الخاصّة. يُمكن الاستفادة من هذه الحقائق المُجمّعة في حالات عديدة. على سبيل المثال، يُمكن إنشاء نموذج إعدادات خادم ويب وتعيين العناوين المُلائمة لمُضيف افتراضي مُحدد دون الآخر، أويُمكن استخدامها في التحكم بمسار الإعدادات، أي تطبيق إعداد معين على حسب حقيقة معينة، فعند معرفة أن النظام هو نظام أوبونتو، فسيكون اسم الخدمة الخاصّة بخادم الويب هي apache2، وفي حال كان نظام التشغيل هو CentOS، فستكون الخدمة الخاصّة بخادم الويب هي httpd. الأمثلة السابقة هي تبسيط لما يُمكن عمله باستخدام هذه الحقائق المُجمّعة. لاستعراض قائمة الحقائق والّتي تمّ جمعها بشكلٍ تلقائيّ على خادم العميل يُمكن استخدام الأمر التّالي: facterكيفيّة تنفيذ مَلفّ البيان الرئيسي The Main Manifestتقوم العُملاء الخاصّة بالأداة Puppet بالاتصال بالخادم المَركزيّ Puppet master دوريًا كل 30 دقيقة تقريبًا، مُرسلةً حقائق factsعن نفسها إليه، بالإضافة إلى سحب فهرس (catalog) مَلفات البيان، والتي هي قائمة مُجمّعة (compiled) للمَوارِد وحالتها المَطلوبة والمُرتبطة مع هذه العُملاء، وتُحدّد هذه القائمة من قِبل مَلفّ البيان الرئيسيّ main manifest، وفيما بعد تُحاول نقاط العُملاء تطبيق التَغَيُّرات لتُلائم الحالة المَطلوبة. يستمر التسلسل السابق بالتكرار طالما أنّ الخادم المَركزيّ يعمل ومُتصل مع نقاط العُملاء. تنفيذ التَغَيُّرات على نقاط العُملاء بشكلٍ يدويٍّ ومُباشر يُمكن المُبادرة بعمل اتصال يدويّ بين أحد نقاط العُملاء والخادم المَركزيّ، رُبما لتسريع عملية التطبيق أو في سبيل تحديد مُشكلة ما، أو لمعرفة كيف سوف يؤثّر مَلفّ البيان على أحد الخوادم دون تطبيقه على جميع الخوادم، ويتمّ ذَلِك عبر بتنفيذ الأمر التّالي على الخادم العميل: puppet agent --testسيقوم الأمر السابق بتطبيق مَلفّ البيان الرئيسي main manifest على الخادم العميل، كما يَعرض هذا الأمر المُخرجات التّالية: Info: Caching certificate_revocation_list for ca Info: Retrieving plugin Info: Caching catalog for host1 Info: Applying configuration version '1423707894' Info: Creating state file /var/lib/puppet/state/state.yaml Notice: Finished catalog run in 0.01 seconds تطبيق مَلفِ بيانٍ كيفيٍ ولمرّة واحدةيُتيح الأمر puppet apply تنفيذ مَلفات بيانٍ اختياريّة لاترتبط بمَلف البيان الرئيسيّ، وعلى نقاط العُملاء مُعينة. sudo puppet apply /etc/puppet/modules/test/init.ppتشغيل مَلفّات البيان بهذا النمط مفيدٌ عندما يكون المُراد اختبار مَلفّ بيان جديد على أحد العُملاء، أو عندما يَكون المُراد تنفيذ مَلفّ البيان لمرّة واحدة فقط، مُثلًا لاستهلال العميل إلى حالة مُعينة. إنشاء مَلفّ بيانٍ رئيسيٍّ مُبسَطتمّت الإشارة سابقًا إلى أنّ مَلفّ البيان الرئيسي على الخادم المَركزيّ من المُفترض تواجده في المسار /etc/puppet/manifests/ وبالاسم site.pp. سيتمّ إضافة بعض التعديلات البسيطة لهذا المَلف: sudo vi /etc/puppet/manifests/site.ppالسطور التّالية تصف مَورِد مَلفّ file resource: file {'/tmp/example-ip': # التصريح عن مَورد مَلفّ ومسار المَلف ensure => present, # التأكد من وجود المَلف mode => 0644, # تحديد صلاحيات المَلف content => "Here is my Public IP Address: ${ipaddress_eth0}.\n", # تحديد مُحتوى المَلف واستخدام الحقائق }يقوم مَلفّ البيان السابق بإنشاء مَلفٍ في المسار /tmp/، والتأكد من وجوده وبالاسم example-ip مع صلاحيات -rw-r--r--، ومُحتوى نصيّ يتضمّن عنوان الـIP الخاصّ بالخادم العميل. بالإمكان إما الانتظار حتّى يقوم الخادم العميل بالاتصال مع الخادم المَركزيّ بشكل آليٍّ، أوتنفيذ الأمر puppet agent –test على أحد نقاط العُملاء. لمُشاهدة التغييرات التي قام بها مَلفّ البيان السابق، وبعد ذلك يُمكن تنفيذ الأمر التّالي على الخادم العميل: Cat /tmp/example-ipوالذي سوف يَعرض السطرَ التّالي مع اختلاف العنوان بطبيعة الحال: Here is my Public IP Address: 10.0.2.15.استهداف عميلٍ مُحددمن المُهم معرفة أنّه بالإمكان تعريف مَورد مُحدد لعميلٍ مُعين دون الآخرين، ولذلك على الخادم المَركزيّ، ومنه المَلف site.pp: sudo vi /etc/puppet/manifests/site.ppسيتمّ إضافة السطور التّالية: node 'ns1', 'ns2' { # تطبيق المَورد التّالي على الخوادم المُحددة فقط file {'/tmp/dns': # التصريح عن مَورد مَلفّ ومسار المَلف ensure => present, # التأكد من وجود المَلف mode => 0644, # تحديد صلاحيات المَلف content => "Only DNS servers get this file.\n", } } node default {} # تطبيق المَوارد على النقاط التي لم تُعرّف بشكل مُحدد.ستتأكّد الأداة Puppet من وجود المَلف /tmp/dns على الخادمين "ns1" و“ns2”، ومن المُمكن تطبيق الأمر puppet agent –test على الخادمين آنفي الذّكر لعدم الانتظار إلى حين الطلب التلقائي من العُملاء. يجدُر الذكر هنا أنّه في حال عدم تعريف مَورد، فإن الأداة Puppet سوف لن تقوم بإجراء أيّة تعديلات على العُملاء، أي في حال حذف المَوارد السابقة من مَلفّ البيان، فإنّ الأداة Puppet لن تَحذف الملفات التي قامت بإنشائها سابقًا، إلا إذا تمّ التصريح بذلك عبر تعديل قيمة ensure إلى absent والّتي تعني غائب أو لا وجود له. في حقيقة الأمر لايُظهر تطبيق الأمثلة السابقة قوة الأداة Puppet وما الذي يُمكنها فعله، ولكنه يُبرهن على الأقل أنّها تعمل كما هو مُخططٌ لها. استخدام الوحدات Modulesتُستخدم الوحدات modules لتجميع المهام في صيغة واحدة، ويتوفّر العديد من هذه الوحدات في مُجتمع Puppet، مع العلم أنّه يُمكن كتابة وحدة جديدة وعدم التقيد بما يوفره المُجتمع من هذه الوحدات. سيتمّ تنصيب وحدة مُعدّة مُسبقًا من موقع Puppet باستخدام الأمر puppetlabs-apache: sudo puppet module install puppetlabs-apacheتحذير: لا يُفترض استخدام الوحدة السابقة في حال وجود خادم الويب Apache، وذلك من شأنه أنّ يُلغي أيّة إعدادات تُدار من قبل الأداة Puppet. يجب الآن تحرير المَلف site.pp، لكي يَستخدم الوحدة المُنصبة (apache). وعلى سبيل المثال سيتمّ اختيار الخادم “host2” لتنصيب خادم الويب Apache لتوضيح المثال: node 'host2' { class { 'apache': } # use apache module apache::vhost { 'example.com': # define vhost resource port => '80', docroot => '/var/www/html' } } ستقوم الأداة Puppet بتنصيب خادم الويب Apache على العميل “host2”، وإعداد مُضيف افتراضيّ “example.com”، مُصغيًا على المنفذ 80، مع document root بالمسار /var/www/html. على الخادم “host2” يُمكن تنفيذ الأمر التّالي لتسريع عمليّة التطبيق: sudo puppet agent --testيَعرض الأمر السابق مُخرجات تُشير إلى تنصيب خادم الويب Apache. بعد استكمال التنصيب، من المُمكن التأكد من عمل خادم الويب بالشكل المطلوب عبر زيارة صفحة الترحيب الافتراضيّة الخاصّة به. خاتمةتمّ التطرّق في هذا الجزء إلى كيفيّة تنصيب الأداة Puppet، وإعدادها إعدادًا أوليًا، وبذلك أصبح بالإمكان التعمّق في المزيد من التفاصيل الخاصّة بهذه الأداة وماتقدمه من تسهيلات لمُدير النظام، وذلك عبر مَلفّات البيان والوحدات الّتي سيتمّ التركيز عليها في الجزء الثاني من هذه السلسلة. ترجمة -وبتصرّف- للمقال How To Install Puppet To Manage Your Server Infrastructure1 نقطة
-
1 نقطة
-
السلام عليكم ورحمة الله نصيحتي هي أن تقرأي كتاب " كيف يتعلم المبرمج بشكل صحيح" للمؤلف :وجدي عصام عبد الرحيم الكتيب يتألف من 33 صفحة ، لكن غني بالنصائح والمعلومات المفيدة لبدأ تعلم البرمجة . بالتوفيق إن شاء الله1 نقطة
-
مرحباً @هبة حمدان سبب عدم تغير القيمة الإفتراضية للإسم وهو أن نمط الوراثة في هذه الحالة protected أي أن الدوال و المتغيرات في الكلاس الأب المُعرفة على أنها public ستُصبح protected في الكلاس الإبن و بالتالي فإن الدالة setName ستُصبح محمية أي أنه لا يُمكنك إستخدامها في الدالة الرئيسية main بإمكانك تمرير الإسم من خلال بناء الكلاس Customer بهذا الشكل: class Customer{ protected: string name; public: Customer(string name = "Unknown"){ this->name = name; } string getName(){ return name; } void setName(string name = "Unknown"){ this->name = name; } }; و بالتالي عند إنشاء كائن من الصنف Customer يمكننا تحديد الإسم له، أيضا سنقوم بتوريث البناء للكلاس Invoice و تكون الطريقة كما هي موضحة أدناه: class Invoice : protected Customer{ private: float Total = 0; public: TV TVs[2]; Invoice(string name) : Customer(name){ for(int i=0;i<2;i++){ Total += TVs[i].price; } } } و في الدالة الرئيسية main يُمكننا عمل التالي: int main() { Invoice* x[2]; for(int i=0; i<2;i++){ x[i] = new Invoice("Invoice " + to_string(i)); // قمنا بتحديد الإسم هنا x[i]->Print(); x[i]->getMin(); cout<<"********************************"<<endl; } return 0; } بخصوص هذا الجزء إعادة التحميل أو overloading تسمح لنا بتعريف دوال تحمل نفس الإسم لكن بمُعاملات مُختلفة لِنقم بإعادة تحميل الدالة Print بهذا الشكل: void Print(string file) { ofstream f; f.open (file); f << "The Name is : " << name << endl; f << "Total is : " << Total << endl; f << "TVs: " << endl; for(int i=0;i<2;i++){ f << "TVs[" << i << "] = (Number = " << TVs[i].getNo() << ", Price = " << TVs[i].getPrice() << ")" << endl; } f.close(); } و هنا قمنا بإنشاء ملف بإسم قيمة المُعامل المُمرر و قمنا بملئه بجميع المتغيرات المحلية و الموروثة و في الأخير قمنا بإغلاق الملف. ليُصبح الكود بالكامل بهذا الشكل: #include <iostream> #include <fstream> using namespace std; /*---------------------------------------------------*/ class Customer{ protected: string name; public: Customer(string name = "Unknown"){ this->name = name; } string getName(){ return name; } void setName(string name = "Unknown"){ this->name = name; } }; /*---------------------------------------------------*/ class TV{ private: int no; public: float price; TV(){ cout << "The TV Number is : " << endl; cin >> no; cout << "The TV Price is : " << endl; cin >> price; } float getPrice(){ return price; } int getNo(){ return no; } void setPrice(float p){ price = p; } }; /*---------------------------------------------------*/ class Invoice : protected Customer{ private: float Total = 0; public: TV TVs[2]; void ReadPrice(){ for(int i=0;i<2;i++){ cout << "TVs[" << i << "] = (Number = " << TVs[i].getNo() << ", Price = " << TVs[i].getPrice() << ")" << endl; } }; int getTotal(){ return Total; } Invoice(string name) : Customer(name){ for(int i=0;i<2;i++){ Total += TVs[i].price; } } void Print(){ cout << "The Name is : " << name << endl; cout << "Total is : " << Total << endl; cout << "TVs: " << endl; this->ReadPrice(); } void Print(string file) { ofstream f; f.open (file); f << "The Name is : " << name << endl; f << "Total is : " << Total << endl; f << "TVs: " << endl; for(int i=0;i<2;i++){ f << "TVs[" << i << "] = (Number = " << TVs[i].getNo() << ", Price = " << TVs[i].getPrice() << ")" << endl; } f.close(); } int getMin(){ float min; min = TVs[0].price; for(int i=0 ;i < 2; i++){ if (TVs[i].price < min) min = TVs[i].price; } cout<<"The minimum :" << min <<endl; return min; } }; /*---------------------------------------------------*/ int main() { Invoice* x[2]; //customer y; for(int i=0; i<2;i++){ x[i] = new Invoice("Invoice " + to_string(i)); string filename = "output" + to_string(i) + ".txt"; x[i]->Print(filename); x[i]->getMin(); cout<<"********************************"<<endl; } return 0; } بإمكانك تجربة المثال من خلال هذا الرابط: هنا بالتوفيق.1 نقطة
-
شكرا جزيلا جزاك الله خير عندي اختبار بكره عن هذا الموضوع وساعدتني المقاله في تجميع افكاري وفهم الموضوع بصوره اوضح1 نقطة
-
أظن أن قراءة كتاب خاص بالبرمجة شيء قديم وأمر ممل نسبيا, ففي مجال البرمجة هنالك لكل لغة برمجة موقع خاص بها لشرحها بكل تفاصيلها وهذا سيكون كمرجع لكي في حال نسيتي شيئا ما بعد أن تقومي بتعلم ما تريدين تعلمه وهو ايضا جيد للتعلم في حال قرأتي منه حيث أن مثلا الكود المشروح يمكنكي تطبيقه وتجربته والتعديل عليه في نفس الوقت. صحيح أن الكتب هي المصدر الأساسي للتعلم ولكن هنالك ما هو أسهل من ذلك في هذا الوقت حيث توجد وكما ذكرت أعلاه المواقع المخصصة للغة معينة أو الوثيقة الرسمية للغة البرمجة, الدورات ,المقالات ومجموعات مواقع التواصل الاجتماعي حيث ان هنالك العديد من الصفحات والمجموعات تقدم بعض المعلومات الجميلة والقيمة. وأنا أنصحك بدورة التي بعنوان CS50 المقدمة من جامعة هارفرد فهي تشرح العديد من الأمور اللازم تعلمها في مجال البرمجة ولكن هذه الدورة باللغة الانجليزية ولكن هنالك من قام بشرحها باللغة العربية. وبما أنكِ ذكرتِ أنكِ تعلمتي واجهات الويب يجب عليكي تطبيق بعض المشاريع في هذا المجال ثم يمكنك بعد ذلك الانتقال إلى تطوير الويب حتى تكتمل خبرتك في مجال الويب والتي تحتوي على البرمجة التي تبحثين عنها.1 نقطة
-
بعد أن قمتي بأخذ دورة تطوير واجهات المستخدم يمكنك أن تبدأي في القراءة عن html و css و JavaScript أيضًا يمكنك أن تبدأي بالقراءة عن مواضيع مثل UI/UX أي واجهة المستخدم و تجربة المستخدم و مواضيع متعلقة بالتصميم. لا أنصح باستخدام الكتب في لتعلم البرمجة في الحقيقة فهي في معظم الأحيان تكون قديمة و محتواها غير مواكب للتحديثات من الأفضل قراءة المقالات.و يوجد العديد من المصادر التي يمكنك القراءة منها مثل "دروس و مقالات" الخاصة بأكاديمية حسوب إذا كنت تحبين القراءة باللغة العربية أو من مدونات مثل css tricks و hackernoon و غيرها إذا كنت تحبين القراءة باللغة الإنجليزية. أيضًا يوجد بعض الكتب الجيدة إذا كنت تفضلين قراءة الكتب مثل Eloquent javascript أيضًا يوجد مجموعة من الكتب الجيدة في أكاديمية حسوب متعلقة بأكثر من لغة يمكنك أن تبدأي بالقراءة منها فأغلب هذه الكتب حديثة و مفيدة.1 نقطة
-
هذا التشتت طبيعي في بداية تعلم لغة Javascript وأيضاً عامل أنها أول لغة برمجة تتعلمها فهذا عامل أيضاً قد يؤدي إلى التشتت وعدم فهم كتابة الكود جيداً وفي البداية الأهم تعلم الأساسيات وهذا ما تفعله الأن أكمل تعلمك للأساسيات ولكن تعلمك للأساسيات ليس معنى ذلك أنك تستطيع التفاعل مع صفحات الويب بصورة كاملة ولكن يجب تعلم تقنياتان لكي تستطيع التعامل مع صفحات الويب وهم Javascript Dom و Javascript Bom . Javascript Dom : مقصود بكلمة DOM هو Document Object Model ويقصد بها أوامر javascript المتعلقة بتصميم صفحات الويب ومن خلالها سوف تستطيع تربط الأوامر التي تعلمتها من قبل الأساسيات مع رموز html وإنشاء صفحات تفاعلية . Javascript Bom : المقصود بكلمة BOM هو Browser Object Model والمقصود هنا أوامر javascript التي تتعامل مع المتصفح نفسه والتي تتيح لك توفير معلومات سوف تستفيد منها في التصميم مثل معرفة حجم صفحة المتصفح وأشياء سوف تساعدك في التصميم أيضاً . الأن يمكنك التركيز والتطبيق جيداً على فهم الأساسيات الأن تتسأل ماذا سوف أفعل بها غداً بعد تعلمك لتقنيات bom و dom سوف تحتاج كتابة هذه الأساسيات كثيراً ثم عليك مشاهدة فيديوهات وقراءة مقالات عن التطبيق العملي . من بعد ذلك يمكنك الدخول إلى تعلم Ecma6 : وهي تعتبر إصدار حديث لكتابة أكواد javascript بطريقة احترافية ومن مميزاتها تحسين الكود وتقليل الأخطاء البرمجية في الكتابة وإنشاء العديد من الأوامر بصورة أبسط من طريقة كتابة javascript القديمة . ويمكن أيضاً بعد ذلك تعلم إحدى إطارارت العمل التالية vuejs ,angular, react وهم عبارة عن إطارات عمل برمجية مبنية على javascript هي أداة تتوفر على عدة مكونات تساعد على وضع أساس للبرمجيات من خلال وظائف جاهزة.1 نقطة
-
قد تَكُون البرمجة (programming) صعبة نوعًا ما، وذلك كغَيْرها من الأنشطة المُفيدة والجديرة بالاهتمام، ومع ذلك، فهي عادة ما تَكُون مُجْزيّة ومُمتعة. عند كتابة برنامج (program)، لابُدّ أن تُخبِر الحاسوب بكُل تَفصيلة صغيرة يَنبغي له تَّنْفيذها، وبصورة صحيحة تمامًا؛ وذلك لأنه سيَتَّبِع البرنامج كما هو مَكتوب تمامًا وبطريقة عمياء. إذن، كيف تُكتَب البرامج غَيْر البسيطة؟ في الواقع، إنه ليس أمرًا غامضًا، فالأمر بِرُمَّته يَعتمِد فقط على تَعَلُّم طريقة التفكير الصحيحة. يُعدّ البرنامج (program) تعبيرًا عن فكرة (idea)، حيث يبدأ المُبرمج بفكرة عامة عن مُهِمّة (task) معينة يُريد من الحاسوب تَّنْفيذها، وعادةً ما يكون المُبرمج على عِلم بكيفية تَّنْفيذ تلك المُهِمّة يدويًا، أو لديه تَصَوُّر عام على الأقل. تتمحور المشكلة حول طريقة تَحْوِيل هذا التَصَوُّر العام إلى إِجراء (procedure)، واضح، ومُكتمِل، ومُحدَّد الخُطوات لتََّنْفيذ تلك المُهِمّة. يُسمَى مثل هذا الإِجراء باسم "الخوارزمية (algorithm)"، والتي هي إِجراء واضح، ومُحدَّد الخُطوات، والذي لابُدّ أن ينتهي دائمًا بَعْد عَدَد مُتناهي (finite number) من الخُطوات؛ فنحن لا نُريد عدّ الإِجراءات التي قد تَستمِر للأبد. لاحِظ أن الخوارزمية (algorithm) تَختلف عن البرنامج (program)، فالبرنامج يُكتَب بلغة برمجية معينة، أما الخوارزمية، فتُكتَب بأيّ لغة بما فيها الإنجليزية، وهي أَشْبَه بالفكرة وراء البرنامج، ويُقصَد بالفكرة هنا مجموعة الخُطوات المطلوب القيام بها حتى يَتمّ تَّنْفيذ المُهِمّة، وليس مُجرَّد مُلخَّص لما ينبغي للمُهِمّة إِنجازه في النهاية. عند وصف الخوارزمية، ليس من الضروري أن تَكُون الخُطوات مُفَصَّلة، وذلك طالما كانت واضحة بما فيه الكفاية لبَيَان أن تَّنْفيذها سوف يُنجِز المُهِمّة المطلوبة، ولكن بالطبع لا يُمكِن التعبير عنها كبرنامج (program) فِعليّ بدون مَلْئ جميع تلك التفاصيل. من أين تأتي الخوارزميات؟ ينبغي عادةً تطويرها، وهو ما يَتطلَّب كثيرًا من التفكير والعمل الجاد. يُمكِن القول أن عملية تطوير الخوارزميات (algorithm development) هي مهارة تُكتسَب مع المُمارسة المستمرة، ولكن، مع ذلك، تَتَوفَّر بعض التقنيات (techniques) والقواعد الإرشادية (guidelines) التي يُمكِنها مُساعدتك. سنتناول في هذا القسم بَعضًا منها، وبالأخص ما يَتعلَّق بالبرمجة "في نطاق ضيق"، كما سنعود للحديث عن نفس هذا الموضوع أكثر من مرة بالفصول القادمة. الشيفرة الوهمية والتصميم المتدرج عند البرمجة "في نطاق ضيق"، فأنت مُقيَّد نوعًا ما باِستخدَام عَدَد قليل من الأوامر، وهي كالتالي: المُتَغيِّرات (variables)، وتَعْليمَات الإِسْناد (assignment statements)، والبرامج (routines) الخاصة بعَمليتي الإِدْخال (input) والإِخراج (output). قد يَتوفَّر لك أيضًا اِستخدَام بعض البرامج الفرعية (subroutines)، والكائنات (objects)، أو رُبما حتى بعض اللَبِنات الأساسية الآخرى، ولكن بشَّرْط أن تَكُون قد كُتِبَت مُسْبَّقًا إِمّا بواسطتك أو بواسطة شخص آخر (لاحِظ أن برامج الإِدْخال والإِخراج تَقع ضِمْن هذا التصنيف). والآن، تَستطِيع بناء مُتتالية مِنْ تلك التَعْليمَات البسيطة، أو ربما قد تَدمجهم دِاخل بُنَى تحكُّم (control structures) أكثر تعقيدًا، مثل تَعْليمَة حَلْقة التَكْرار (loops) while، وتَعْليمَة التَفْرِيع الشَّرْطيّة if. لنفْترِض أننا نريد برمجة الحاسوب ليُنفِّذ مُهِمّة (task) معينة، يُمكِننا البدء بكتابة تَوصِيف (description/specification) مَبدئي لتلك المُهِمّة، بحيث يُلخِّص وظيفة الخوارزمية (algorithm) المطلوب تَطْويرها، ثُمَّ نُضيف مزيدًا من الخطوات والتفاصيل إلى ذلك التَوصِيف تدريجيًا، وعبر سِلسِلة من التصميمات المُحسَّنة، إلى أن نَصِل إلى الخوارزمية الكاملة التي يُمكِن ترجمتها مباشرة إلى لغة برمجية. تُكتَب عادة تلك التَوصِيفات باِستخدَام ما يُعرَف باسم الشيفرة الوهمية (pseudocode)، وهي مجموعة من التَعْليمَات العَاميَّة، التي تُحاكِي بِنْية اللغات البرمجية، بصورة مُبسَّطة، وبدون قواعد الصيغة (syntax) الصارمة المُعتادة بالشيفرة الفعليّة. يُطلَق على هذا الأسلوب من كتابة البرامج اسم التصميم المُتدرج (stepwise refinement)، ويُصنَّف ضِمْن استراتيجيات التصميم من أعلى لأسفل (top-down design). لنفْحَص كيفية تَطبيق التصميم المتدرج لكتابة إحدى البرامج التي قد تَعرَّضنا لها خلال القسم السابق. يَحسِب هذا البرنامج قيمة الاستثمار خلال خمسة أعوام. يُمكِن تَوصِيف مُهِمّة البرنامج بالعبارة التالية: "اِحسب قيمة الاستثمار واطبعها لكل عام من الأعوام الخمسة التالية، بحيث تُحدَّد قيمة الاستثمار المبدئي وسعر الفائدة مِن قِبَل المُستخدِم". ربما نُفكر بكتابة الشيفرة الوهمية التالية: // اقرأ مدخل المستخدم Get the user's input // احسب قيمة الاستثمار بعد عام Compute the value of the investment after 1 year // اطبع القيمة Display the value // احسب قيمة الاستثمار بعد عامين Compute the value after 2 years // اطبع القيمة Display the value // احسب قيمة الاستثمار بعد ثلاث أعوام Compute the value after 3 years // اطبع القيمة Display the value // احسب قيمة الاستثمار بعد أربع أعوام Compute the value after 4 years // اطبع القيمة Display the value // احسب قيمة الاستثمار بعد خمس أعوام Compute the value after 5 years // اطبع القيمة Display the value على الرغم من أن الخوارزمية بالأعلى سليمة وستؤدي الغرض منها، لكنها مُكرَّرة، وهو ما يَعنِي ضرورة اِستخدَام حَلْقة تَكْرار (loop)؛ لأنها ستُمكِّننا من كتابة شيفرة مُعمَّمة أكثر وبعَدَد سطور أقل. يُقصَد بالتَعْميم (generalization) هنا أنه يُمكِن اِستخدَام نفس حَلْقة التَكْرار بغض النظر عن عدد الأعوام المطلوب مُعالجتها. والآن، سنُعيد كتابة مُتتالية الخطوات السابقة كالتالي: // اقرأ مدخل المستخدم Get the user's input // طالما ما يزال هناك عدد من الأعوام للمعالجة while there are more years to process: // احسب قيمة الاستثمار بعد العام التالي Compute the value after the next year // اطبع القيمة Display the value على الرغم من أن الخوارزمية (algorithm) بالأعلى سليمة، لكنها مُوجَزة، وربما مُبهَمة بشكل أكثر من اللازم. يَحتاج الحاسوب عمومًا إلى تَعْليمَات واضحة وصريحة، ولهذا سنحتاج، مثلًا، إلى شرح الخطوات: "اقرأ مُدْخَل المُستخدِم" و "احسب قيمة الاستثمار بَعْد العام التالي" و "ما يزال هناك عَدَد من الأعوام للمعالجة". فمثلًا، نستطيع إعادة تَوصِيف الخطوة "اِقْرأ مُدْخَل المُستخدِم" إلى التالي: // اسأل المستخدم عن قيمة الاستثمار المبدئي Ask the user for the initial investment // اقرأ مدخل المستخدم Read the user's response // اسأل المستخدم عن قيمة سعر الفائدة Ask the user for the interest rate // اقرأ مدخل المستخدم Read the user's response أمَا بخُصوص الخطوة "احسب قيمة الاستثمار بَعْد العام التالي"، فسنحتاج إلى معرفة طريقة حِسَابها (ينبغي أن تَطلُب في تلك الحالة مزيد من التوضيح من أستاذك أو مُديرك)، ولكن دَعْنَا الآن نفْترِض أن قيمة الاستثمار تُحسَب بإضافة قيمة فائدة معينة إلى قيمة الاستثمار السابقة، وبالتالي يُمكِننا إِعادة كتابة حَلْقة التَكْرار while كالتالي: // طالما ما يزال هناك عدد من الأعوام للمعالجة while there are more years to process: // احسب الفائدة Compute the interest // أضف الفائدة إلى قيمة الاستثمار Add the interest to the value // اطبع القيمة Display the value نحتاج الآن إلى توضيح الاختبار الموجود بالخطوة "ما يزال هناك عَدَد من الأعوام للمعالجة"، وهو ما يُمكِن القيام به عن طريق عدّ الأعوام بأنفسنا، سنَستخدِم عَدَّادًا قيمته تُساوِي الصفر، ثم نُزيِد قيمة هذا العَدَّاد بمقدار الواحد بَعْد كل مرة نُعالِج فيها عامًا جديدًا، ونَتوقَف عندما تُصبِح قيمة العَدَّاد مُساوِية للعَدَد المطلوب من الأعوام. يُطلَق على ذلك عادةً اسم "حَلْقة العدّ (counting loop)"، وهي أحد الأنماط الشائعة، ولذلك تَوقَّع أن تَستخدِم شيئًا مُشابهًا بكثير من البرامج. تُصبِح الآن حَلْقة التَكْرار while كالتالي: // ابدأ بعدد أعوام يساوي الصفر years = 0 // طالما عدد الأعوام أقل من الخمسة while years < 5: // أزد عدد الأعوام بمقدار الواحد years = years + 1 // احسب الفائدة Compute the interest // أضف الفائدة إلى تلك القيمة Add the interest to the value // اطبع القيمة Display the value نَحتاج إلى أن نكون أكثر توضيحًا بخُصوص طريقة حِسَاب الفائدة، وسنفْترِض أنها تُساوِي حاصل ضرب سعر الفائدة بقيمة الاستثمار الحالية. نُضيِف هذا الإيضاح إلى ذلك الجزء من الخوارزمية المسئول عن قراءة مُدْخَلات المُستخدِم، وبهذا، نَحصُل على الخوارزمية الكاملة: // اسأل المستخدم عن قيمة الاستثمار المبدئي Ask the user for the initial investment // اقرأ مدخل المستخدم Read the user's response // اسأل المستخدم عن قيمة سعر الفائدة Ask the user for the interest rate // اقرأ مدخل المستخدم Read the user's response // ابدأ بعدد أعوام يساوي الصفر years = 0 // طالما عدد الأعوام أقل من الخمسة while years < 5: // أزد عدد الأعوام بمقدار الواحد years = years + 1 // احسب الفائدة بحيث تساوي حاصل ضرب القيمة مع سعر الفائدة Compute interest = value * interest rate // أضف الفائدة إلى تلك القيمة Add the interest to the value // اطبع القيمة Display the value وَصلنا إلى النقطة التي يُمكِن معها الترجمة المُباشرة إلى لغة برمجة مناسبة، فقط نحتاج إلى اِختيار أسماء المُتَغيِّرات (variables)، وتَقْرير نص العبارات التي سنَطبَعها للمُستخدِم، وهكذا. نستطيع الآن كتابة الخوارزمية (algorithm) بلغة الجافا كالتالي: double principal, rate, interest; // التصريح عن المتغيرات int years; System.out.print("Type initial investment: "); principal = TextIO.getlnDouble(); System.out.print("Type interest rate: "); rate = TextIO.getlnDouble(); years = 0; while (years < 5) { years = years + 1; interest = principal * rate; principal = principal + interest; System.out.println(principal); } ما زال أمامنا بعض التَحسِّينات الإضافية، مِن بينها تَضْمِين هذه الشيفرة داخل برنامج كامل، وإضافة التعليقات (comments)، وطِباعة المَزيد من المعلومات للمُستخدِم، ولكنه يظِلّ نفس البرنامج بالقسم السابق. في حين تَستخدِم خوارزمية الشيفرة الوهمية (pseudocode algorithm) المسافات البادئة (indentation) لتوضيح التَعْليمَات الواقعة ضِمْن حَلْقة التَكْرار (loop)، تُهمِل لغة الجافا هذه المسافات البادئة تمامًا، ولهذا أَضفنا قوسين معقوصين (curly brackets/braces) {} لتوضيح أيّ مجموعة تَعْليمَات (statements) تقع ضِمْن حَلْقة التَكْرار. إذا لم تَستخدِم هذه الأقواس بشيفرة الجافا، فإن الحاسوب سيفْترِض أن التَعْليمَة الوحيدة الواقعة ضِمْن حَلْقة التَكْرار هي years = years + 1;، أمَا بقية التَعْليمَات فسيُنفِّذها مرة واحدة فقط بَعْد انتهاء حَلْقة التَكْرار. للأسف، لا يُبلِّغ الحاسوب عن هذه النوعية من الأخطاء، بنفس الطريقة التي يُبلِّغ بها عن حُدوث خطأ في حالة عدم اِستخدَام القوسين الهلاليين (rounded brackets/parentheses) () حول (years < 5)؛ وذلك لأن تلك الأقواس مطلوبة وِفقًا لصيغة (syntax) تَعْليمَة while، أمَا قوسيّ المعقوصين {}، فإنها مطلوبة فقط لأغراض دَلاليّة (semantics)، أيّ أغراض مُتعلِّقة بالمعنى. يَستطيع الحاسوب عمومًا تَمييز أخطاء بناء الجملة (syntax errors) فقط، لا الأخطاء الدَلاليّة (semantic errors). لاحِظ أن التَوصِيف الأصلي للمسألة التالي لم يَكُن مُكتملًا: ينبغي لك عمومًا، قَبْل بدء كتابة أيّ برنامج، أن تتأكد من أن لديك التَوصِيف الكامل لوظيفة البرنامج المطلوب كتابته، فلابُدّ أن تَعرِف المعلومات التي سيَقرأها البرنامج (input) وأيّ خَرْج (output) ينبغي أن يَطبعُه، وكذلك الحِسَابات التي ينبغي له القيام بها. ربما يُمكِننا إعادة تَوصِيف نفس البرنامج السابق بصورة أكثر معقولية كالتالي: مسألة متتالية "3N+1" لنفْحَص مثالًا آخرًا لم نتَعرَّض له من قَبْل، السؤال هنا عبارة عن مَسألة رياضية مُجرَّدة، والتي يَعُدّها الكاتب تَحْدِيدًا واحدة مِن تمارينه البرمجية المُفضلة. سنبدأ هذه المرة، بعكس المثال السابق، بتَوصِيف كامل (specification) لمُهِمّة (task) البرنامج: اُكْتُب برنامجًا يَقرأ عَدَدًا صحيحًا موجبًا من المُستخدِم، ثُمَّ يَطبَع مُتتالية الأعداد "3N+1"، بحيث تبدأ من العَدَد المُدْخَل، كما يَنبغي للبرنامج أن يعدّ عَدَد عناصر المُتتالية ويَطبعها." اُنظر الخوارزمية المبدئية التالية، والتي تُوضِح فقط التَصَوُّر العام لمثل هذا البرنامج: // اقرأ عدد صحيح موجب من المستخدم Get a positive integer N from the user. // احسب قيمة كل عنصر بالمتتالية، واطبعه وعدّه Compute, print, and count each number in the sequence. // اطبع عدد عناصر المتتالية Output the number of terms. يَتضح لنا أن الخطوة الثانية تَحتوِي على المَضمون الفِعليّ للبرنامج، وبالطبع تحتاج إلى مزيد من الإيضاح. لمّا كنا نُريد الاستمرار بحِسَاب قيم عناصر المُتتالية حتى تُصبِح قيمة N الحالية مُساوِية للعَدَد ١، فإننا سنحتاج ببساطة إلى اِستخدَام حَلْقة تَكْرار (loop)، ولذلك دَعْنَا نُعيد صياغة نفس الجملة السابقة بحيث تَتوافق مع حَلْقة التَكْرار while. إننا في حاجة إلى مَعرِفة متى نَستمِر بتَّنْفيذ حَلْقة التَكْرار ومتى نُوقِّفها، في الواقع، سنستمر طالما كانت قيمة N الحالية لا تُساوِي ١، ولهذا يُمكِننا إعادة كتابة خوارزمية الشيفرة الوهمية (pseudocode algorithm) كالتالي: // اقرأ عدد صحيح موجب من المستخدم Get a positive integer N from the user; // طالما كانت قيمة `N` الحالية لا تساوي 1 while N is not 1: // احسب قيمة عنصر المتتالية التالي واسنده إلى N Compute N = next term; // اطبع قيمة N Output N; // عدّ عنصر المتتالية Count this term; // اطبع عدد عناصر المتتالية Output the number of terms; لمّا كان حِسَاب قيمة عنصر المُتتالية التالي يَعتمِد على ما إذا كانت قيمة N الحالية هي عَدَد زوجي (even) أم فردي (odd)، يَعنِي ذلك أن الحاسوب بحاجة إلى تَّنْفيذ حَدَثين مُختلفين لكل حالة، وهو ما يَعنِي أن اِستخدَام تَعْليمَة التَفْرِيع الشَّرْطيّة if بات ضروريًا؛ وذلك للاختيار ما بين تلك الحالتين، اُنظر الخوارزمية بَعْد التعديل: // اقرأ عدد صحيح موجب من المستخدم Get a positive integer N from the user; // طالما كانت قيمة `N` الحالية لا تساوي 1 while N is not 1: // إذا كان N عددًا زوجيًا if N is even: // احسب قيمة العنصر التالي وأسنده إلى N Compute N = N/2; // إذا كان N عددًا فرديًا else // احسب قيمة العنصر التالي وأسنده إلى N Compute N = 3 * N + 1; // اطبع قيمة N Output N; // عدّ عنصر المتتالية Count this term; // اطبع عدد عناصر المتتالية Output the number of terms; انتهينا تقريبًا، يَتبقَّى فقط العدّ (counting)؛ وذلك لطباعة عَدَد عناصر المُتتالية. يَعنِي العدّ ببساطة أن تبدأ بالقيمة صفر، ثم تُضيف المقدار واحد في كل مرة يَكُون لديك فيها ما تعدّه، ولهذا نحتاج إلى مُتَغيِّر (variable) للقيام بالعدّ، يُعرَف باسم العَدَّاد (counter). يَنبغي ضَبْط قيمة ذلك المُتَغيِّر إلى القيمة صفر قَبْل بداية الحَلْقة (loop)، بحيث تَزداد (increment) تلك القيمة أثناء تَّنْفيذ الحَلْقة. (يُعدّ ذلك أحد الأنماط الشائعة [common pattern]، ولذلك تَوقَّع أن تراه بكثير من البرامج). تُصبِح الخوارزمية، بَعْد إضافة العَدَّاد (counter)، كالتالي: // اقرأ عدد صحيح موجب من المستخدم Get a positive integer N from the user; // اضبط قيمة العداد إلى القيمة صفر Let counter = 0; // طالما كانت قيمة N الحالية لا تساوي 1 while N is not 1: // إذا كان N عددًا زوجيًا if N is even: // احسب قيمة العنصر التالي وأسنده إلى N Compute N = N/2; // إذا كان N عددًا فرديًا else // احسب قيمة العنصر التالي وأسنده إلى N Compute N = 3 * N + 1; // اطبع قيمة N Output N; // أزد قيمة العداد بمقدار 1 Add 1 to counter; // اطبع عدد عناصر المتتالية Output the counter; ما يزال أمامنا مشكلة أخيرة بخُصوص الخطوة الأولى، وهي كيف نتأكد من أن المُستخدِم قد أَدْخَل عَدَدًا صحيحًا موجبًا؟ ففي الواقع، قد يُدْخِل المُستخدِم عَدَدًا سالبًا أو صفرًا، وعندها سيستمر تَّنْفيذ البرنامج للأبد؛ لأن القيمة المُدْخَلة N، في تلك الحالة، لن تُصبِح أبدًا مُساوِية للواحد. ربما قد لا يُعدّ ذلك مشكلة ضخمة في تلك الحالة تَحْدِيدًا، ولكن، مع ذلك، ينبغي عمومًا محاولة كتابة برامج غَيْر قابلة للخطأ. نستطيع حَل تلك المُشكلة عن طريق الاستمرار بقراءة الأعداد إلى أن يُدْخِل المُستخدِم عددًا صحيحًا موجبًا. // اطلب من المستخدم إدخال عدد صحيح موجب Ask user to input a positive number; // أسند القيمة المدخلة إلى N Let N be the user's response; // طالما N ليست موجبة while N is not positive: // اطبع رسالة خطأ Print an error message; // اقرأ قيمة أخرى واسندها إلى N Read another value for N; // اضبط قيمة العداد إلى القيمة صفر Let counter = 0; // طالما كانت قيمة `N` الحالية لا تساوي 1 while N is not 1: // إذا كان N عددًا زوجيًا if N is even: // احسب قيمة العنصر التالي وأسنده إلى N Compute N = N/2; // إذا كان N عددًا فرديًا else // احسب قيمة العنصر التالي وأسنده إلى N Compute N = 3 * N + 1; // اطبع قيمة N Output N; // أزد قيمة العداد بمقدار 1 Add 1 to counter; // اطبع عدد عناصر المتتالية Output the counter; لاحِظ أن حَلْقة while الأولى ستنتهي فقط عندما تُصبِح قيمة N زوجية. عند محاولة كتابة شيفرة التَوصِيف التالي: "إذا كانت قيمة N غَيْر زوجية، اُطلب من المُستخدِم إِدْخَال عدد آخر"، يقع الكثير من المبرمجين، وبالأخص المبتدئين، في خطأ اِستخدَام تَعْليمَة التَفْرِيع if بدلًا من تَعْليمَة حَلْقة التَكْرار while. تَظهر المشكلة تَحْدِيدًا عندما يُدْخِل المُستخدِم عددًا غَيْر زوجي مرة آخرى. لمّا كانت تَعْليمَة التَفْرِيع if تُنفَّذ مرة واحدة فقط، فإنه لا يَتمّ فَحْص مُدْخَل المُستخدِم إلا مرة واحدة فقط، مما يعني أن البرنامج سينتقل إلى تَّنْفيذ التَعْليمَة التالية بغض النظر عما إذا كانت قيمة المُدْخَل الثاني للمُستخدِم زوجية أم لا، وهو ما يَتسبَّب بحُدوث حَلْقة لا نهائية (infinite loop) كما ذَكرنا آنفًا. أمَا في حالة اِستخدَام حَلْقة التَكْرار while، فإن الحاسوب سيَقفِز (أو سيَنقِل التَحكُّم بتعبير أدق) إلى بداية الحَلْقة بَعْد كل عملية إِدْخَال؛ لاختبار ما إذا كانت القيمة المُدْخَلة زوجية أم لا، مما يَعنِي أنه سيستمر في طلب إِدْخَال عَدَد جديد إلى أن يُدْخِل المُستخدِم قيمة مقبولة، أيّ عدد زوجي. وبالتالي، في حالة انتقال البرنامج إلى تَّنْفيذ ما بَعْد حَلْقة while، فإن قيمة N هي زوجية حتمًا. ها هو نفس البرنامج بشيفرة الجافا. لاحِظ اِستخدَام العَامِلين (operators) <= بمعنى "أقل من أو يُساوِي" و != بمعنى "لا يُساوِي"، بالإضافة إلى اِستخدَام التعبير N % 2 == 0؛ لاختبار ما إذا كانت قيمة N زوجية. نُوقِشَت كل هذه العَوامِل في القسم ٢.٥. import textio.TextIO; public class ThreeN1 { public static void main(String[] args) { int N; // لحساب العناصر بالمتتالية int counter; // لعد عدد عناصر المتتالية System.out.print("Starting point for sequence: "); N = TextIO.getlnInt(); while (N <= 0) { System.out.print( "The starting point must be positive. Please try again: " ); N = TextIO.getlnInt(); } // نعلم أن N هي عدد صحيح موجب عند هذه النقطة counter = 0; while (N != 1) { if (N % 2 == 0) N = N / 2; else N = 3 * N + 1; System.out.println(N); counter = counter + 1; } System.out.println(); System.out.print("There were "); System.out.print(counter); System.out.println(" terms in the sequence."); } // نهاية main } // نهاية الصنف ThreeN1 مُلاحظتان أخيرتان على هذا البرنامج: أولًا، ربما لاحَظت أن البرنامج لم يَطبَع قيمة أول عنصر بالمُتتالية -أيّ قيمة N المُدْخَلة من قِبَل المُستخدِم-، وكذلك لم يعدّها. هل هذا خطأ؟ يَصعُب القول. ربما ينبغي أن نَطرح سؤالًا آخر: هل كان تَوصِيف البرنامج (specification) صريحًا بما يَكفي بخُصوص تلك النقطة؟ في الواقع، للإجابة على مثل هذا السؤال، ستَحتاج إلى طَلَب مزيد من الإيضاح من أستاذك/مديرك. يُمكِن عمومًا حل هذه المشكلة -في حال كانت- بسهولة، فقط اِستبدل السَطْرين التاليين بتَعْليمَة counter = 0 قَبْل حَلْقة التَكْرار while : System.out.println(N); // print out initial term counter = 1; // and count it ثانيًا، لماذا تُعدّ هذه المسألة تَحْدِيدًا مثيرة؟ في الواقع، يَجِدْ كثير من علماء الرياضيات والحاسوب هذه المسألة مُشوقة؛ بسبب سؤال بسيط، يَخُص تلك المسألة، والذي لم يَتوصَّلوا للإجابة عليه بَعْد. السؤال هو "هل عملية حِسَاب قيم مُتتالية '3N+1' دومًا ما ستنتهي بَعْد عَدَد مُتناهي (finite) من الخطوات لجميع قيم N المبدئية؟" على الرغم من سهولة حِسَاب قيم المُتتاليات بشكل مُفرد، لم يُجِبْ أحد على السؤال الأعم حتى الآن، أيّ بصياغة آخرى، لا أحد يَعلم ما إذا كان من الصحيح تسمية عملية حِسَاب قيم مُتتالية "3N+1" بـ"الخوارزمية (algorithm)"؛ فبالنهاية، لابُدّ لأيّ خوارزمية أن تَنتهي بَعْد عَدَد مُتناهي من الخطوات. لاحظ: يَنطبق ذلك على الأعداد الصحيحة (integers) بمفهومها الرياضي، وليس القيم من النوع العددي الصحيح int! بمعنى أننا نفْترِض هنا أن قيمة N قد تَكُون أيّ عدد صحيح مُمكن مهما كَبُر، وهو ما لا يَنطبق على مُتَغيِّر من النوع int داخل برنامج بلغة الجافا. إذا أَصبحت قيمة N كبيرة جدًا ليَتمّ تَمثيلها بمُتَغيِّر من النوع int (32 بت)، فلا يُمكِن عدّ قيم خَرْج البرنامج صحيحة رياضيًا، أيّ أن البرنامج لا يَحسِب قيم متتالية "3N+1" بشكل صحيح عندما تَكُون قيمة N كبيرة. اُنظر تمرين ٨.٢. كتابة الشيفرة (coding) والاختبار (testing) وتنقيح الأخطاء (debugging) بَعْد انتهائك من تَطوير خوارزمية البرنامج (algorithm)، سيَكُون من اللطيف لو كان بإمكانك الضغط فقط على زِر معين؛ لتَحصُل بَعْدها على برنامج قابل للتَّنْفيذ (working program) بصورة ممتازة. في الواقع، عملية تَحْوِيل الخوارزمية إلى شيفرة بلغة الجافا لا تَتمّ دومًا بمثل هذه السَلاسَة لسوء الحظ، وحتى عندما تَصِل إلى تلك المرحلة من الحُصول على برنامج قابل للتَّنْفيذ (working program)، فإنه غالبًا ما يَكُون قابلًا للتَّنْفيذ بمعنى أنه يُنفِّذ "شيء ما"، لا بالضرورة الشيء الذي تريده أن يَفعَله. بَعْد الانتهاء من تصميم البرنامج (program design)، يَحيِن موعد كتابة الشيفرة (coding): أيّ ترجمة التصميم إلى برنامج مكتوب بلغة الجافا أو بأيّ لغة برمجية اخرى. مَهمَا كنت حَريصًا أثناء كتابة الشيفرة، فعادةً ما ستَجِدْ بعض أخطاء بناء الجملة (syntax errors) طريقها إلى الشيفرة، ولذلك سيَرفُض مُصرِّف (compiler) الجافا البرنامج، وسيُبلِّغك عن نوع معين من رسائل الخطأ (error message). لاحِظ أنه في حين يستطيع المُصرِّف اكتشاف أخطاء بناء الجملة (syntax errors) دائمًا، فإنه لسوء الحظ ليس بنفس الكفاءة في اكتشاف مَاهية الخطأ، بل أنه قد لا يَتَمكَّن، في بعض الأحيان، من معرفة مكان حُدوث الخطأ الفِعليّ، فمثلًا، قد يَتسبَّب وجود خطأ إملائي أو نَسْيَان قوس "{" بالسطر رقم ٤٥ بتَوَقُّف المُصرِّف بالسطر ١٠٥. يَظِلّ، مع ذلك، الفهم الجيد لقواعد صياغة (syntax rules) اللغة البرمجية مع اِتباع بعض القواعد الإرشادية البرمجية البسيطة الطريقة الأفضل لتَلافِي كثير من تلك الأخطاء. لنسْتَعْرِض بعضًا من تلك القواعد، أولًا، لا تَكتُب أبدًا قوس حَاصِرة "{" بدون كتابة زوجه الآخر "}"، ثُمَّ عُد بَعْدها لكتابة التَعْليمَات بينهما؛ وذلك لأن نَسْيَان قوس أو إضافة قوس في غَيْر مَحَلّه يُعدّ من أكثر الأخطاء التي يَصعُب اكتشافها خاصة بالبرامج الضخمة. ثانيًا، اِستخدِم دائما المسافات البادئة (indentation) لتَنسيق الشيفرة، وإن عَدَّلت البرنامج، عَدِّل أيضًا المسافات البادئة بحيث تُصبِح مُتوافقة مع التَعْدِيل الجديد. ثالثًا، اِستخدِم نَمط تَسمية (naming scheme) ثابت؛ حتى لا تُعانِي بَعْد ذلك بينما تَتَذكَّر ما إذا كان اسم مُتَغيِّر ما (variable) هو "interestrate" أم "interestRate". رابعًا، عندما يُبلِّغك المُصرِّف بأكثر من رسالة خطأ (error message) واحدة، لا تُحاوِل إصلاح رسالة الخطأ الثانية حتى تَنتهي من إِصلاح الأولى؛ لأن المُصرِّف عادةً ما يَرتبك بعد إِيجاده لأول خطأ، ولذلك قد تَكُون رسائل الخطأ التالية هي مُجرَّد تَخمينات. وأخيرًا، وربما هي النصيحة الأفضل: خُذ الوقت الكافي لفِهم الخطأ قبل مُحاولة إِصلاحه؛ فالبرمجة، بالنهاية، ليست علمًا تجريبيًا (experimental science). إذا تمّ تَصرِيف برنامجك بنجاح، لا يَعنِي ذلك أنك قد انتهيت؛ فمن الضروري أن تَختبر (test) البرنامج لتتأكد مما إذا كان يَعمَل بشكل صحيح، وهو ما لا يَقْتصِر على مُجرَّد الحُصول على الخَرْج الصحيح (output) لعينة المُدْخَلات (inputs) التي أَعطاك إِياها الأستاذ، بل ينبغي للبرنامج أن يَعمَل بشكل سليم لجميع المُدْخَلات المقبولة، وفي حالة اِستقباله لمُدَْخَل غَيْر صالح، فينبغي أن يُوبِخ البرنامج المُستخدِم بلطف، لا أن يَنَهار (crashing) تمامًا. عمومًا، ينبغي أن تَختبِر البرنامج على نطاق واسع من المُدْخَلات. قد تُحاوِل أيضًا إِيجاد مجموعة المُدْخَلات التي بإِمكانها اِختبار جميع الوظائف التي أَدْرَجتها بالشيفرة. في حالة كتابة برامج كبيرة، حَاوِل تقسيمها إلى عدة مراحل، بحيث تَختبِر كل مرحلة قَبْل البدء بالمرحلة التالية، حتى لو اضطررت لكتابة شيفرة إِضافية تقوم بالاختبار مثل أن تَستدعِي أحد البرامج الفرعية التي قُمت بكتابتها للتو؛ فأنت حتمًا لا تُريد أن يَنتهي بك الحال بخُمسمائة سَطْر جديد من الشيفرة مَصحوبة بخطأ ما في مكان ما. الغرض من الاختبار (testing) هو مُحاولة العُثور على الأخطاء البرمجية (bugs)، وهي -بعكس أخطاء التَصرِيف (compilation errors)- أخطاء دَلاليّة (semantic errors)، أيّ تَكُون في صورة سُلوك غَيْر سليم. المُحزن في الأمر هو أنك غالبًا ما ستَجِدهم. تستطيع، مع ذلك، تَقْليل -لا التَفادِي التام- هذه النوعية من الأخطاء البرمجية (bugs)، من خلال الانتباه أثناء قيامك بكُلًا من التصميم (design)، وكتابة الشيفرة (coding). عندما تَكتشف خطأً برمجيًا (bug)، يَحيِن موعد تَنْقِيح الأخطاء (debugging)، بمعنى تَعَقُّب سبب الخطأ بالشيفرة بهدف التخلص منه. لاحظ أنك لن تُصبِح خبيرًا بتَنْقِيح الأخطاء إلا من خلال المُمارسة المستمرة؛ فهي مَهارة تكتسبها، مثلها مثل جميع نواحي البرمجة الآخرى، ولذلك لا تَكُن خائفا منها، وإنما تَعلَّم منها. تُعدّ القدرة على قراءة الشيفرة واحدة من أهم مهارات تَنْقِيح الأخطاء الاساسية، والمقصود بقراءة الشيفرة هنا: القدرة على تَنْحية تَصوراتك المُسْبَّقة عما ينبغي للشيفرة أن تقوم به، وبدلًا من ذلك، تَعقُّب الطريقة التي يُنفِّذها بها الحاسوب خَطوة بخَطوة؛ لتُدرِك ما تَقوم به فِعليًا. في الواقع، هذا ليس بالأمر السهل. ما يزال الكاتب يَذكُر تلك المرة التي قضى فيها عدة ساعات يَبحث عن خطأ برمجي ليَكتشِف بالنهاية أن سَطْرًا ما بالشيفرة، كان قد نَظَر إليه عشرات المرات، يَحتوِي على القيمة ١ بدلًا من الحرف i، أو تلك المرة التي كَتَب فيها برنامجًا فرعيًا (subroutine) اسمه هو WindowClosing، والذي كان سيُؤدي غرضه تمامًا لولا أن الحاسوب كان يَبحث عن البرنامج الفرعي windowClosing (بحرف w صغير). قد يُساعِدك أحيانًا الاستعانة بشخص آخر لفَحْص الشيفرة، خاصة وأنه لن يَملك نفس تَصوراتك المُسْبَّقة عنها. أحيانًا ما يَكُون مُجرَّد العثور على ذلك الجزء من البرنامج الذي يَكمُن فيه الخطأ مُشكلة بحد ذاته، ولهذا تُوفِّر مُعظم بيئات التطوير البرمجية (programming environments) برنامجًا يُسمَى مُنقِّح الأخطاء (debugger)؛ لمساعدتك بالعثور على الأخطاء البرمجية (bugs)، بحيث يَتمّ تَشْغِيل البرنامج (program) تحت تَحكُّم ذلك المُنقِّح، والذي يَسمَح لك بضَبْط ما يُعرَف باسم نقاط المُقاطعة (breakpoints)، وهي نقاط بالبرنامج سيَتوقَّف عندها المُنقِّح مؤقتًا (pause)؛ حتى تَتَمكَّن من فَحْص قيم مُتَغيِّرات البرنامج عند تلك النقطة، مما سيُساعدك في العُثور على المكان الذي بدأت فيه الأخطاء البرمجية بالظهور أثناء تَّنْفيذ البرنامج. بمُجرَّد تَحْدِيد ذلك الجزء من البرنامج الذي يَكمُن فيه الخطأ البرمجي (bug)، يَسمَح لك المُنقِّح أيضًا بتَّنْفيذ البرنامج سَطْرًا سَطْرًا، ومِنْ ثَمَّ، تستطيع مشاهدة ما يَحدُث تفصيليًا. يَعترِف الكاتب أنه لا يَستخدِم مُنقِّح الأخطاء دائمًا، وإنما يَتبِع المنهج التقليدي لتَنْقِيح الأخطاء، وذلك بإضافة تَعْليمَات تَنْقِيحيّة (debugging statements) داخل البرنامج، والتي هي مُجرَّد تَعْليمَات خَرْج تَطبَع معلومات عن حالة (state) البرنامج. عادةً ما تُكتَب أيّ تَعْليمَة تَنْقِيحيّة على الصورة التالية: System.out.println("At start of while loop, N = " + N); ينبغي لنص الخَرْج أن يُساعِدك على تَحْدِيد مكان تَعْليمَة الطباعة المسئولة عن ذلك الخَرْج، وكذلك معرفة قيم المُتَغيِّرات (variables) المُهمة. ستكتشف، في بعض الأحيان، أن الحاسوب لم يَمر حتى على ذلك الجزء من البرنامج الذي كنت تَظن أنه يُنفِّذه. تَذكَّر أن الهدف هو اكتشاف أول نُقطة بالبرنامج أَصبَحت فيها حالة البرنامج (state) مُخالِفة للحالة المُتوقَّعة، فهذا هو مكان الخطأ البرمجي (bug). وأخيرًا، تَذكَّر القاعدة الذهبية لتَنْقِيح الأخطاء: إذا كنت مُتأكدًا تمامًا أن كُل شئ بالبرنامج سليم، ومع ذلك ما يزال لا يَعمَل بالصورة المطلوبة، فلابُدّ أن واحدًا من تلك الأشياء هو ببساطة خطأ. ترجمة -بتصرّف- للقسم Section 3.1 Blocks, Loops, and Branches من فصل Chapter 3: Programming in the Small II: Control من كتاب Introduction to Programming Using Java.1 نقطة
-
تعتمد قدرة الحاسب على أداء مهامٍ معقدة على دمج التعليمات البسيطة ضمن بنى تحكمٍ. هناك 6 بنى تحكم كهذه في جافا، تستخدم لتحديد تدفق التحكم الطبيعي في البرنامج وتكفي ثلاث منها لكتابة برامج قادرة على أداء أيّة مهمة.بنى التحكم الستّ هي: الكتلة (block) وحلقة while وحلقة do..while وحلقة for وتعليمة if وتعليمة switch. تُعدّ كلّ واحدة من البنى السابقة "تعليمةً" مفردة، لكنها في الواقع تعليمة بنيوية قد تحتوي بداخلها تعليمة أو أكثر. الكتل الكتلة (Blocks) هي الشكل الأبسط للتعليمات البنيوية، وتهدف إلى تجميع سلسلة من التعليمات في تعليمة واحدة. تأخذ الكتلة الشكل التالي: { // ضع التعليمات هنا } بكلمات أخرى، تتألف الكتلة من سلسلة من التعليمات المُغلّفة بين قوسين من الشكل "{ }". يمكن ألّا تحتوي الكتلة أيّة تعليمة، وتدعى عندئذٍ بالكتلة الفارغة (empty block) ومن الوارد أن تلزمك أحيانًا. تتألف الكتلة الفارغة من زوج فارغ من الأقواس فقط. عادةً ما ترد كتل التعليمات ضمن تعليمات أخرى وتهدف إلى تجميع عدة تعليمات معًا في وحدةٍ واحدة. يمكنك في الحقيقة استخدام كتل التعليمات أينما وردت تعليمةٌ. بيد أنّ من الواجب استخدامها في حالة البرامج الفرعية -لاحظ استخدامنا لها سابقًا في حالة البرنامج الفرعي main . البرنامج الفرعيّ هو كتلةٌ بالتعريف، نظرًا لكونه سلسلةً من التعليمات المغلّفة ضمن زوج من الأقواس. من الجدير بالذكر هنا أنّ لغة البرمجة جافا هي لغةٌ حرة التنسيق وهذا يعني عدم وجود قواعد صياغة تحدد كيفيّة ترتيب اللغة على الصفحة. يمكنك إن أردت، على سبيل المثال، كتابة كتلةٍ كاملة على سطر واحد. مع ذلك، ينبغي عليك، اتباعًا لممارسات البرمجة الفضلى، تنظيم برنامجك على نحوٍ يجعل قراءته وفهمه أسهل ما يمكن. يقتضي هذا في الحالة العامة كتابةً تعليمةٍ واحدة فقط في السطر واستخدام المسافات البادئة للإشارة إلى التعليمات المُحتواة ضمن بنى التحكم. سيُستخدم التنسيق هذا في جميع أمثلة الكتاب.إليك مثالين عن الكتل: { System.out.print("الجواب هو: "); System.out.println(ans); } { // تستبدل هذه الكتلة بين قيمتي المتغيرين x و y int temp; // مُتغير مؤقت لاستخدامه ضمن هذه الكتلة temp = x; // احفظ نسخةً عن x صمن temp. x = y; // انسخ قيمة y إلى x. y = temp; // انسخ قيمة temp إلى y. } قمنا في المثال الثاني بالتصريح عن المتغيّر temp ضمن الكتلة. التصريح عن متغيّرٍ ضمن كتلةٍ أمرٌ جائزٌ لا بل ومستحسنٌ أيضًا لا سيما إن كان استخدام المتغير مقتصرًا على تلك الكتلة. لا يمكن الوصول للمتغير المُصرّح عنه ضمن كتلة خارجها ولا يكون مرئيًّا إلا فيها. عندما ينفّذ الحاسوب تعليمة التصريح عن متغيّر، يخصص موضعًا في الذاكرة لتخزين قيمة هذا المتغيّر (نظريًّا على الأقل). عندما تنتهي الكتلة، يتم التخلّص من موضع الذاكرة ذاك ليصبح مُتاحًا للاستخدام مجددًا. يقال أنّ هذا المتغير "محلّي" ضمن الكتلة. هناك مفهوم أكثر شمولًا يُصطلح على تسميته مجال المُعرّف (scope of an identifier). مجال المُعرّف هو جزء من البرنامج يكون فيه المعرّف صالحًا. يقتصر مجال المتغيّر المُعرّف ضمن كتلة على الكتلة نفسها، وعلى نحوٍ أدق، يقتصر مجال المتغيّر المعرّف ضمن كتلة على الجزء من الكتلة الذي يلي التصريح عنه. حلقة while الأساسيّة لا تؤثر تعليمة الكتلة بحدّ ذاتها على تدفق التحكم في البرنامج على عكس بنى التحكم الخمس المتبقية. تُصنّف هذه البنى إلى نوعين: تعليمات الحلقات وتعليمات التفريع. في الواقع، لا تحتاج لغة البرمجة عامّة الغرض سوى إلى بنية تحكم واحدة من كل نوعٍ وكل ما سواهما هو بغرض التسهيل والتبسيط ليس إلّا. سنتطرق في هذا القسم إلى حلقة while وتعليمة if ونؤجل الحديث عن بنى التحكم الثلاث المتبقية إلى أقسام لاحقة. تُستخدم حلقة while لتكرار تعليمة معيّنة مرة بعد مرة. من غير المرجّح أنّك ستحتاج تكرار العملية إلى الأبد، لكنّه أمرٌ ممكن ويدعى الحلقة اللانهائية وهو غير محبّذ في الحالة العامة. هناك قصة قديمة عن رائدة علوم الحاسوب غريس موراي هوبر التي قرأت التعليمات على علبة الشامبو وكانت تنصّ على: " ارغي، اشطفي، كرري العمليّة." اتبعت غريس التعليمات حسب قولها وانتهى بها الأمر بعلبة شامبو فارغة. روت غريس القصة كمزحة حول اتباع الحواسيب للتعليمات بدون تفكير. توخيًّا للدقة، نقول أنّ حلقة while تُكرر التعليمة مرةً بعد مرة طالما أنّ قيمة شرطٍ محدّدٍ هي true. تأخذ حلقة while الشكل الآتي: while (تعبير منطقي) //ضع تعليماتك هنا نظرًا لأنّه من الممكن والمرّجح أن التعليمة هي كتلة تعليمات، تأخذ معظم حلقات while الشكل الآتي: while (تعبير منطقي) { //ضع تعليماتك هنا } يظّن بعض المبرمجين أنّ من الواجب تضمين الأقواس دائمًا لضرورات تنسيقية حتى لو كانت ستُغلّف تعليمةً واحدة فحسب، لكننا لن نتبع هذه النصيحة في الكتاب. أمّا دلالة تعليمة while فهي كما يلي: عندما يصادف الحاسوب تعليمة while، يقوم بتقييم التعبير المنطقي لينتج عن ذلك قيمة إما ture أو false. إذا كانت القيمة false، يتجاوز الحاسوب بقيّة حلقة while ويتابع تنفيذ الأمر الذي يليها في البرنامج. أمّا إذا كانت قيمة التعبير ture، ينفّذ الحاسوب التعليمة أو كتلة التعليمات الواردة ضمن الحلقة. بعدئذٍ، يعود الحاسوب إلى بداية حلقة while ويكرر العملية (أي يعاود تقييم التعبير المنطقي، يُنهي التنفيذ إذا كانت قيمته false، ويستمر إذا ما كانت ture). تتكرر العملية مرةً بعد مرةٍ بعد مرة حتى تصبح قيمة التعبير false عند قيام الحاسوب بتقييمه. إن لم نصل لهذه النتيجة، فستستمر الحلقة إلى الأبد.إليك مثالًا عن حلقة while بسيطة تطبع الأرقام 1، 2، 3، 4، 5: int number; // العدد المراد طباعته. number = 1; // ابدأ من الرقم 1. while ( number < 6 ) { // تابع طالما أن العدد أصغر من 6. System.out.println(number); number = number + 1; // انتقل للعدد التالي. } System.out.println("انتهى التنفيذ"); ترجمة -بتصرّف- للقسم Section 2: Algorithm Development من فصل Chapter 3: Programming in the Small II: Control من كتاب Introduction to Programming Using Java.1 نقطة