اذهب إلى المحتوى

البحث في الموقع

المحتوى عن 'الاستثناءات'.

  • ابحث بالكلمات المفتاحية

    أضف وسومًا وافصل بينها بفواصل ","
  • ابحث باسم الكاتب

نوع المحتوى


التصنيفات

  • الإدارة والقيادة
  • التخطيط وسير العمل
  • التمويل
  • فريق العمل
  • دراسة حالات
  • التعامل مع العملاء
  • التعهيد الخارجي
  • السلوك التنظيمي في المؤسسات
  • عالم الأعمال
  • التجارة والتجارة الإلكترونية
  • نصائح وإرشادات
  • مقالات ريادة أعمال عامة

التصنيفات

  • مقالات برمجة عامة
  • مقالات برمجة متقدمة
  • PHP
    • Laravel
    • ووردبريس
  • جافاسكربت
    • لغة TypeScript
    • Node.js
    • React
    • Vue.js
    • Angular
    • jQuery
    • Cordova
  • HTML
  • CSS
    • Sass
    • إطار عمل Bootstrap
  • SQL
  • لغة C#‎
    • ‎.NET
    • منصة Xamarin
  • لغة C++‎
  • لغة C
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • لغة Kotlin
  • لغة Rust
  • برمجة أندرويد
  • لغة R
  • الذكاء الاصطناعي
  • صناعة الألعاب
  • سير العمل
    • Git
  • الأنظمة والأنظمة المدمجة

التصنيفات

  • تصميم تجربة المستخدم UX
  • تصميم واجهة المستخدم UI
  • الرسوميات
    • إنكسكيب
    • أدوبي إليستريتور
  • التصميم الجرافيكي
    • أدوبي فوتوشوب
    • أدوبي إن ديزاين
    • جيمب GIMP
    • كريتا Krita
  • التصميم ثلاثي الأبعاد
    • 3Ds Max
    • Blender
  • نصائح وإرشادات
  • مقالات تصميم عامة

التصنيفات

  • مقالات DevOps عامة
  • خوادم
    • الويب HTTP
    • البريد الإلكتروني
    • قواعد البيانات
    • DNS
    • Samba
  • الحوسبة السحابية
    • Docker
  • إدارة الإعدادات والنشر
    • Chef
    • Puppet
    • Ansible
  • لينكس
    • ريدهات (Red Hat)
  • خواديم ويندوز
  • FreeBSD
  • حماية
    • الجدران النارية
    • VPN
    • SSH
  • شبكات
    • سيسكو (Cisco)

التصنيفات

  • التسويق بالأداء
    • أدوات تحليل الزوار
  • تهيئة محركات البحث SEO
  • الشبكات الاجتماعية
  • التسويق بالبريد الالكتروني
  • التسويق الضمني
  • استسراع النمو
  • المبيعات
  • تجارب ونصائح
  • مبادئ علم التسويق

التصنيفات

  • مقالات عمل حر عامة
  • إدارة مالية
  • الإنتاجية
  • تجارب
  • مشاريع جانبية
  • التعامل مع العملاء
  • الحفاظ على الصحة
  • التسويق الذاتي
  • العمل الحر المهني
    • العمل بالترجمة
    • العمل كمساعد افتراضي
    • العمل بكتابة المحتوى

التصنيفات

  • الإنتاجية وسير العمل
    • مايكروسوفت أوفيس
    • ليبر أوفيس
    • جوجل درايف
    • شيربوينت
    • Evernote
    • Trello
  • تطبيقات الويب
    • ووردبريس
    • ماجنتو
    • بريستاشوب
    • أوبن كارت
    • دروبال
  • الترجمة بمساعدة الحاسوب
    • omegaT
    • memoQ
    • Trados
    • Memsource
  • برامج تخطيط موارد المؤسسات ERP
    • تطبيقات أودو odoo
  • أنظمة تشغيل الحواسيب والهواتف
    • ويندوز
    • لينكس
  • مقالات عامة

التصنيفات

  • آخر التحديثات

أسئلة وأجوبة

  • الأقسام
    • أسئلة البرمجة
    • أسئلة ريادة الأعمال
    • أسئلة العمل الحر
    • أسئلة التسويق والمبيعات
    • أسئلة التصميم
    • أسئلة DevOps
    • أسئلة البرامج والتطبيقات

التصنيفات

  • كتب ريادة الأعمال
  • كتب العمل الحر
  • كتب تسويق ومبيعات
  • كتب برمجة
  • كتب تصميم
  • كتب DevOps

ابحث في

ابحث عن


تاريخ الإنشاء

  • بداية

    نهاية


آخر تحديث

  • بداية

    نهاية


رشح النتائج حسب

تاريخ الانضمام

  • بداية

    نهاية


المجموعة


النبذة الشخصية

تم العثور على 2 نتائج

  1. تسهُل كتابة البرامج عادةً بصورةٍ مثاليةٍ عن كتابتها لتكون متينة robust، حيث تتأقلم البرامج المتينة مع أيّ ظروفٍ استثنائيةٍ تواجهها دون أن تنهار crash، كما يمكنك أن تكتبها عن طريق تحديد المشاكل التي يُحتمل أن تقع، وكذلك تضمين الاختبارات الضرورية لكلّ مشكلةٍ منها؛ فمثلًا، إذا كان لدينا مصفوفة A واستخدَم برنامجٌ عنصر المصفوفة A[‎i]‎، فإنه قد ينهار إذا كانت قيمة i خارج النطاق المسموح به لقيم فهارس indices تلك المصفوفة؛ إذًا، ينبغي لأيّ برنامجٍ متينٍ أن يتوقّع احتمالية استخدام فهرسٍ غير صالحٍ، ويوفّر أيضًا الحماية الضرورية؛ ونستطيع أن نكتب برنامجًا يتأكد من أن الفهرس المستخدَم سيقع دائمًا ضِمن النطاق المسموح به بأن يكون الشرْط اللاحق للشيفرة التي تسبِق استخدام عنصر المصفوفة، أو اختبار صلاحية قيمة الفهرس index قبل استخدامها للإشارة إلى عنصرٍ بالمصفوفة كالتالي: if (i < 0 || i >= A.length) { ... // Do something to handle the out-of-range index, i } else { ... // Process the array element, A[i] } تعاني الطريقة السابقة من بعض المشاكل، فمثلًا، يصعُب أحيانًا أن تتوقّع جميع الأمور التي يمكنها أن تتسبّب بالأخطاء، كما أنه لن يتضح ما ينبغي القيام به عندما تكتشِف خطأً معينًا، إذ أن محاولة توقُّع جميع المشاكل المحتملة قد تحوُّل خوارزميةً بسيطةً إلى كتلةٍ متشابكةٍ من تعليمات if الشرْطية. الاستثناءات وأصناف Exception توفّر جافا تقنيةً أكثر تنظيمًا وأناقةً للتعامل مع الأخطاء التي قد تحدُث أثناء تشغيل البرنامج، وعادةً ما يشار إلى تلك التقنية باسم معالجة الاستثناءات exception handling، إذ يُعَد الاستثناء مفهومًا أشمل من الخطأ، حيث يتضمّن أيّ ظروفٍ قد تحدُث أثناء تنفيذ البرنامج وتحيده عن مساره الطبيعي. عمومًا ما يُقال أن البرنامج بلّغ thrown عن استثناء عند حدوث خطأٍ أثناء تنفيذ البرنامج، وفي تلك الحالات يخرُج البرنامج عن مساره الطبيعي وقد ينهار؛ ويمكن تجنُّب ذلك بالتقاط caught الاستثناء ومعالجته بطريقةٍ ما؛ وجرت العادة أن يبلّغ جزءٌ من البرنامج عن الاستثناء فيما يلتقطه جزءٌ آخر، وبينما يتسبّب الاستثناء -إذا لم يُلتقط- في انهيار البرنامج. يمكن للخيوط الأخرى في البرامج متعددة الخيوط multithreaded أن تستمر بالعمل حتى بعد انهيار إحداها؛ فمثلًا، تُعَد برامج واجهة المستخدم الرسومية واحدةٌ من البرامج متعددة الخيوط التي يمكن لبعض أجزائها أن تستمر بالعمل حتى إذا تعطّلت بعض أجزائها الأخرى نتيجةً لحدوث استثناء ما. نظرًا لأن برامج جافا تنفَّذ بواسطة مفسِّر جافا interpreter، لذلك فإن انهيار برنامجٍ معينٍ لا يعني انهيار المفسِّر، وإنما يعني انتهاء البرنامج قبل أوانه، بصورةٍ أكثر تحديدًا يلتقِط المفسِّر أي استثناء لم يلتقطه البرنامج ثم ينهي ذلك البرنامج؛ أما في لغاتٍ برمجيةٍ أخرى فقد يؤدي انهيار برنامج إلى انهيار النظام بأكمله إلى أن يُعاد تشغيله. الأمر الذي يستحيل حدوثه في لغة جافا. لقد تعرّضنا لكل من الاستثناءات وتعليمة try..catch المستخدَمة لالتقاط الاستثناءات ومعالجتها، إلا أنه ما تزال هناك بعض قواعد الصيغة syntax الخاصة بتلك التعليمة التي سنناقشها خلال هذا المقال. يحدُث استثناء عندما يكون الشئ المبلّغ عنه عبارةً عن كائنٍ يحتوي على معلوماتٍ عن تلك النقطة المحدَّدة من البرنامج التي حدث بها الاستثناء، وعادةً ما تتضمّن تلك المعلومات قائمةً بالبرامج الفرعية التي كان البرنامج ينفّذها عندما حدث الاستثناء، والتي تسمّى باستدعاءات المكدّس call stack، ولمّا كان بإمكان أيّ برنامجٍ فرعيٍ أن يستدعي برنامجًا آخر جاز أن يوجد أكثر من برنامجٍ فرعيٍ قيد التنفيذ في نفْس الوقت، وتتضمّن المعلومات رسالة خطأٍ تصِف سبب حدوث الاستثناء، وكما أنها قد تتضمّن بياناتٍ إضافيةٍ أخرى. يجب أن تنتمي الكائنات المبلّغ عنها إلى صنفٍ فرعيٍ subclass من الصنف القياسي java.lang.Throwable، وعمومًا يمثَّل كلّ نوعٍ مختلفٍ من الاستثناءات بواسطة صنفٍ فرعيٍ خاصٍ مُشتقٍ من Throwable، وترتَّب تلك الأصناف الفرعية وفقًا لسلالة أصنافٍ class hierarchy معقدةٍ تُظهر العلاقات بين أنواع الاستثناءات المختلفة، كما يملك الصنف Throwable صنفين فرعيين مباشرين هما Error وException، وبدورهما يملكان الكثير من الأصناف الفرعية الأخرى المعرّفة مسبقًا؛ إذًا يستطيع المبرمِج أن ينشئ أصناف استثناءات جديدةٍ لتمثيل أنواعٍ جديدةٍ من الاستثناءات. تمثِّل أغلب الأصناف الفرعية المشتقّة من الصنف Error أخطاءً خطيرةً في آلة جافا الافتراضية Java virtual machine، كما تتسبّب عادةً في انتهاء البرنامج لعدم توفُّر أي طريقةٍ لمعالجتها، وعمومًا لا يحبَّذ أن تُلتقط تلك النوعية من الأخطاء، فمثلًا يقع الخطأ ClassFormatError عندما تحاول آلة جافا الافتراضية العثور على بياناتٍ غير صالحةٍ في ملفٍ يُفترض أنه يحتوي على شيفرة صنف مصرّفة، فإذا كان الصنف الجاري تحميله جزءًا من البرنامج، فلن يُستكمل البرنامج. بينما تمثِّل الأصناف الفرعية المشتقّة من الصنف Exception استثناءات يمكن التقاطها، وفي حالاتٍ كثيرةٍ تُعَد الأخطاء errors استثناءات أيضًا، إلأ أنها تكون أخطاءً ضمن البرنامج أو ضمن البيانات المُدخَلة والتي يتوقّعها المبرمِج ويستجيب لها بالطريقة المناسبة. يملِك الصنف Exception صنفًا فرعيًا اسمه RuntimeException، ويتضمّن ذلك الصنف على الكثير من أصناف الاستثناءات الشائعة بما في ذلك كلّ الأصناف التي تعرّضنا لها بالأقسام السابقة؛ فمثلًا الصنفان الآتيان: IllegalArgumentException NullPointerException مشتقّان من الصنف RuntimeException، وعمومًا تشير تلك النوعية من أصناف الاستثناءات إلى وجود خطأٍ برمجيٍ bug ينبغي على المبرمِج إصلاحه، بينما يتجاهل البرنامج احتمالية حدوث الاستثناءات من النوعين RuntimeExceptions وError لينهار في حالة وقوعها، الأمر الذي بالفعل في كل مرة يستخدِم فيها البرنامج عنصر مصفوفة A[‎i]‎ بدون أن يستعدّ لالتقاط استثناء من النوع التالي: ArrayIndexOutOfBoundsException وتُعَد معالجة أصناف الاستثناءات الأخرى من غير الصنفين Error وRuntimeExceptions عمليةً إجباريةً mandatory، الأمر الذي سيتضّح معناه بعد قليل. تظهِر سلالة الأصناف الصنف Throwable وعددٌ قليلٌ من أصنافه الفرعية، حيث تتطلّب الأصناف الملونة باللون الأحمر معالجة استثناءات اجباريةٍ mandatory exception-handling، كما في الصورة التالية: يتضمّن الصنف Throwable مجموعةً من توابع النسَخ instance methods التي تستخدمها كائنات الاستثناء، فإذا كان e ينتمي إلى الصنف Throwable أو إلى أيٍ من أصنافه الفرعية، فإن الدالة e.getMessage()‎ تعيد سلسلةً نصيةً من النوع String لتصِف ذلك الاستثناء، وكذلك تعيد الدالة e.toString()‎ التي يستخدمها النظام للتمثيل النصي string representation للكائنات سلسلةً نصيةً من النوع String تتكون من اسم الصنف الذي ينتمي إليه الاستثناء، بالإضافة إلى نفْس السلسلة النصية التي تعيدها الدالة e.getMessage()‎، كما يطبع التابع e.printStackTrace()‎ استدعاءات المكدّس stack trace إلى الخرْج القياسي، الأمر الذي قد يفيد عند التعرّف على سبب مشكلةٍ ما، وفي حالة لم يلتقط البرنامج استثناءً معينًا، ترسَل قائمة الاستدعاءات إلى الخرْج القياسي بصورةٍ افتراضيةٍ. تعليمة Try تلتقِط تعليمة try الاستثناءات في برامج جافا، حيث تشبه تعليمات try التعليمة التالية: try { double determinant = M[0][0]*M[1][1] - M[0][1]*M[1][0]; System.out.println("The determinant of M is " + determinant); } catch ( ArrayIndexOutOfBoundsException e ) { System.out.println("M is the wrong size to have a determinant."); e.printStackTrace(); } سيحاول الحاسوب أن ينفّذ كتلة التعليمات التالية لكلمة try، وإذا لم يحدث استثناء أثناء تنفيذها، فإنه سيتجاهل تمامًا عبارة catch ضمن التعليمة؛ أما إذا حدث استثناء من النوع ArrayIndexOutOfBoundsException فسيقفز الحاسوب إلى عِبارة catch الخاصة بالتعليمة، حيث تعالِج عِبارة catch في هذه الحالة الاستثناء من النوع ArrayIndexOutOfBoundsException إذ ستمنع معالجة الاستثناء بهذه الطريقة البرنامج من الانهيار. تتيح قواعد صيغة تعليمة try الكثير من الخيارات الأخرى، فمثلًا يمكن لتعليمة try..catch أن تتضمّن أكثر من عِبارة catch واحدةٍ، الأمر الذي ما يسمَح بالتقاط أنواعٍ مختلفةٍ من الاستثناءات ضِمن تعليمةٍ try واحدةٍ، ويمكن أن يحدَث استثناء من النوع NullPointerException إذا كانت قيمة M فارغةٌ بالإضافة إلى الاستثناء من النوع ArrayIndexOutOfBoundsException الذي حدَث في المثال السابق، ونعالِج لاستثناءين بإضافة عبارة catch أخرى إلى تعليمة try، هكذا: try { double determinant = M[0][0]*M[1][1] - M[0][1]*M[1][0]; System.out.println("The determinant of M is " + determinant); } catch ( ArrayIndexOutOfBoundsException e ) { System.out.println("M is the wrong size to have a determinant."); } catch ( NullPointerException e ) { System.out.print("Programming error! M doesn't exist." + ); } سيحاول الحاسوب أن ينفّذ التعليمات ضِمن عِبارة try، فإذا لم يقع أيّ خطأٍ، سيتخطّى الحاسوب عِبارتي catch في المثال السابق؛ أما إذا حدث استثناء من النوع ArrayIndexOutOfBoundsException فسينفّذ الحاسوب عِبارة catch الأولى ويتخطّى الثانية، بينما إذا حدث استثناء من النوع NullPointerException، فسينفّذ الحاسوب عِبارة catch الثانية ويتجاهل عِبارة catch الأولى. يُشتّق الصنفان التاليان من الصنف RuntimeException: ArrayIndexOutOfBoundsException NullPointerException ولذلك يسهُل أن تلتقِط جميع الاستثناءات من النوع RuntimeException بعبارة catch واحدةٍ كما في المثال التالي: try { double determinant = M[0][0]*M[1][1] - M[0][1]*M[1][0]; System.out.println("The determinant of M is " + determinant); } catch ( RuntimeException err ) { System.out.println("Sorry, an error has occurred."); System.out.println("The error was: " + err); } ستَلتقط عبارة catch في تعليمة try السابقة أيّ استثناء مشتقٍ من الصنف RuntimeException أو من أصنافه الفرعية؛ ويوضِّح ذلك هدف تنظيم الأصناف الممثِّلة للاستثناءات في هيئة سلالة أصنافٍ، حيث يمكنك أن تضيّق نطاق الالتقاط على نوع استثناء محدّدٍ أو توسّعه ليلتقط نطاقًا أوسع من أنواع الاستثناءات. يتوافق ملتقِط الاستثناءات من النوع RuntimeException مع أنواعٍ كثيرةٍ أخرى غير تلك التي تكون موضع الاهتمام، ولذلك يمكنك أن تدمج أنواع الاستثناءات المتوقّعة ضِمن عِبارة catch واحدةٍ كالتالي: try { double determinant = M[0][0]*M[1][1] - M[0][1]*M[1][0]; System.out.println("The determinant of M is " + determinant); } catch ( NullPointerException | ArrayIndexOutOfBoundsException err ) { System.out.println("Sorry, an error has occurred."); System.out.println("The error was: " + err); } دمجنا في المثال السابق الاستثناءين بواسطة محرِّف الخط العمودي "|" الذي يُستخدم أيضًا لتمثيل العامل المنطقي or، حيث تلتقط عبارة catch في المثال السابق الأخطاء من النوعين الآتيين فقط: NullPointerException ArrayIndexOutOfBoundsException في الحقيقة، المثال السابق غير واقعيٍ؛ لأنه من يستبعَد استخدام معالجة الاستثناءات لتخطّي أخطاءٍ مثل استخدام مؤشرٍ فارغٍ null pointer أو استخدام فهرسٍ غير صالحٍ لمصفوفة، ولذلك عليك أن تتأكّد من أن البرنامج قد أسنَد assign قيمةً غير فارغةٍ للمصفوفة M، فلو كانت لغة جافا ستجبرك على كتابة تعليمة try..catch في كل مرةٍ تستخدم فيها مصفوفة، لكنت ستشعر بالاستياء بلا شك، ولهذا السبب لا تُعَد معالجة الاستثناءات من النوع RuntimeException عمليةً ضروريةً أو إلزاميةً؛ فهناك أمورً كثيرةً قد تقع على نحوٍ خاطئٍ، وعمومًا نستنتج من ذلك أن معالجة الاستثناءات لا تمثِّل حلًا لمشكلة متانة البرامج، وإنما هي مجرّد أداةٍ تساعدك على حلّ المشكلة بطريقة أكثر تنظيمًا في معظم الحالات. نستكمل حديثنا عن قواعد الصيغة الخاصة بتعليمة try، فبالإضافة إلى ما سبق، يمكنك أن تضيف عِبارة finally في النهاية على النحو التالي: try { statements } optional-catch-clauses optional-finally-clause سنكتب عبارة catch بالصيغة التالية: catch ( exception-class-names variable-name ) { statements } قد تمثِّل exception-class-names صنف استثناء واحدٍ أو عدّة أصنافٍ يفصِل بينها المحرِّف "|"، وسنكتب عِبارة finally بالصيغة التالية: finally { statements } تنفَّذ عِبارة finally دائمًا مثل خطوةٍ أخيرةٍ ضِمن تعليمة try، وسواءٌ إذا حدَث استثناء وعولِج أولم يعالَج، أو إذا لم يحدُث من الأساس، ستتضمّن عبارة finally بعض الشيفرة المسئولة عن عمليات التنظيف cleanup التي ستنفَّذ في جميع الأحوال، وأحد الأمثلة على شيفرة التنظيف هو غلْق الاتصال الشبكي المفتوح انظر مثلًا إلى خوارزمية الشيفرة الوهمية (استخدمنا شيفرةً وهميةً لأننا لم نناقش الشبكات بعد) التالية: try { // افتح اتصال شبكي open a network connection // تفاعَل عبر الاتصال communicate over the connection } catch ( IOException e ) { // بلِّغ عن خطأ report the error } finally { // إذا كان الاتصال قد فُتح بنجاح if the connection was successfully opened // أغلِق الاتصال close the connection } تتأكّد عِبارة finally من أن الاتصال الشبكي قد أُغلق بصوةٍ مؤكَّدةٍ سواءٌ حدَث خطأٌ أو لم يحدُث أثناء الاتصال، حيث تتبّع الشيفرة الوهمية في المثال السابق نمطًا pattern شائعًا عندما تتعامل مع الموارد resources، فتحصل أولًا على المورِد ثم استخدامه وبعد ذلك تحرِّره release في نهاية الأمر. يشتَرط لنفعّل ذلك الخيار أن يمثَّل المورد بواسطة كائنٍ ينفِّذ implement واجهة interface تُعرف بواجهة AutoCloseable، حيث تعرِّف تابعًا اسمه close()‎ دون أية معامِلات parameters؛ وعمومًا تنفِّذ أصناف جافا القياسية الممثِّلة لأشياء مثل الملفات والاتصالات الشبكية تلك الواجهة بالفعل، كما ينفِّذ الصنف Scanner تلك الواجهة، وستستخدِم الشيفرة التالية النمط السابق لتتأكّد من غلْق كائنٍ من الصنف Scanner بصورةٍ تلقائيةٍ: try( Scanner in = new Scanner(System.in) ) { // استخدم الصنف لتقرأ الدخل القياسي } catch (Exception e) { // حدثت بعض الأخطاء أثناء استخدام الصنف } يُشترط للتعليمة التي تنشِئ كائنًا من الصنف Scanner أن توضع ضِمن قوسين بعد كلمة try، وأن يعرّف المتغيّر variable declaration الموجود بها وأن تتضمّن تهيئةً initialization مبدئيةً لقيمة المتغيّر، بمعنى أن يكون المتغيّر محليًا local، كما يمكنك أن تعرِّف عِدة متغيّراتٍ يُفصل بينها بفاصلةٍ منقوطةٍ ضِمن هذين القوسين؛ وتضمن الطريقة السابقة نجاح استدعاء النظام للدالة in.close()‎ في نهاية تعليمة try بشرْط أن يُهيأ الكائن الممثِّل للصنف Scanner بنجاح. ما تزال هناك خياراتٌ أخرى توفّرها تعليمة try، ويمكنك أن تطّلع على برنامج TryStatementDemo.java، حيث يتضمّن أمثلةً على جميع تلك الخيارات،كما ستجد الكثير من التعليقات التي قد تساعدك على فهْم ما قد يحدُث عند تنفيذ البرنامج. التبليغ عن الاستثناءات Throwing Exceptions قد يبلِّغ البرنامج نفْسه في بعض الأحيان عن اسستثناء بصورةٍ متعمَّدة، فمثًا قد يكتشِف البرنامج نوعًا معينًا من الأخطاء التي لا يجد طريقةً معقولةً ليعالجها ضِمن تلك النقطة من البرنامج التي حدثت فيها المشكلة، وفي تلك الحالة، يستطيع البرنامج أن يبلِّغ عن استثناء على أمل أن يلتقطه جزءٌ آخرٌ من البرنامج ويعالجه؛ وسنستخدِم تعليمة throw لذلك، حيث تُكتب على النحو التالي: throw exception-object ; يجب أن ينتمي exception-object إلى أحد الأصناف الفرعية المشتقّة من Throwable، وعادةً ما ينتمي إلى صنفٍ فرعيٍ من الصنف Exception على وجه التحديد، ويُنشَأ باستخدام العامل new كالتالي: throw new ArithmeticException("Division by zero"); يمثِّل المُعامل الممرِّر للمنشِأ رسالة الخطأ ضِمن كائن استثناء، فمثلًا إذا كان e يشير إلى ذلك الكائن، فتُستعاد نفْس رسالة الخطأ باستدعاء e.getMessage()‎. قد تجد أن المثال السابق سخيفًا نوعًا ما؛ لأنك قد تتوقّع أن النظام سيبلّغ عن استثناء من النوع ArithmeticException عندما يَقسم عددًا على 0، فإذا كانت الأعداد المقسومة من النوع int، إذًا ستؤدي القسمة على 0 إلى حدوث استثناء من النوع المذكور، بينما إذا تضمنت العمليات الحسابية أعدادًا حقيقيةً، فلن تقع أيّ استثناءاتٍ نهائيًا، وإنما ستُستخدم القيمة الخاصة Double.NaN لتمثِّل عمليةً غير صالحةٍ؛ ومع ذلك قد يُبلّغ أحيانًا عن استثناء من النوع ArithmeticException عند قسمة عددٍ حقيقيٍ على 0. تعالَج الاستثناءات المبلّغ عنها سواءٌ عن طريق النظام أو تعليمة throw بنفْس الطريقة، سنفترض أن لدينا استثناءً بُلغ عنه ضِمن تعليمة try، فإذا احتوت تلك التعليمة على عِبارة catch تتوافق مع نوع الاستثناء المبلّغ عنه، فسيقفز الحاسوب إلى تلك العِبارة وينفّذها، وحينها يعالَج الاستثناء؛ ثم سينفّذ الحاسوب عِبارة finally إذا ضمِّنت في تعليمة try وسيستمر الحاسوب في تنفيذ شيفرة البرنامج بصورةٍ طبيعيةٍ، أي سينتقل إلى التعليمات التالية لتعليمة try، بينما إذا لم يُلتقط الاستثناء ويعالَج على الفور، فسيستمر الحاسوب بعملية معالجة الاستثناء. ينتهي البرنامج بعد تنفيذ عِبارة finally ويحصُل البرنامج routine المستدعي لذاك البرنامج الفرعي على فرصةٍ ليعالِج الاستثناء إذا حدث استثناء أثناء تنفيذ برنامجٍ فرعيٍ، ولم يعالَج بواسطة البرنامج الفرعي؛ فمثلًا، إذا وقَعت عملية استدعاء البرنامج الفرعي ضِمن تعليمة try تتوافق عِبارة catch الموجودة بها مع نوع الاستثناء المبلّغ عنه، فسينفِّذ الحاسوب تلك العِبارة وسيستمر بعدها في تنفيذ البرنامج بصورةٍ طبيعيةٍ، بينما إذا لم يعالِج البرنامج الثاني الاستثناء أيضًا فسينتهي، وحينها سيحصل البرنامج المُستدعي له على فرصته ليعالج الاستثناء وهكذا، وفي حال مرّ الاستثناء بكامل سلسلة استدعاءات البرامج الفرعية دون أن يعالَج، فسينهار البرنامج بالكامل. يُحتمل لأي برنامجٍ فرعيٍ أن يولّد استثناء، ويعلِن عن تلك الحقيقة صراحةً بإضافة العبارة throws exception-class-name إلى تعريفه، انظر المثال التالي: // [1] static public double root( double A, double B, double C ) throws IllegalArgumentException { if (A == 0) { throw new IllegalArgumentException("A can't be zero."); } else { double disc = B*B - 4*A*C; if (disc < 0) throw new IllegalArgumentException("Discriminant < zero."); return (-B + Math.sqrt(disc)) / (2*A); } } [1] يعيد قيمة الجَذر الأكبر للمعادلة التربيعية Axx + Bx + C = 0، فإذا كان A == 0 أو BB - 4AC قيمةً سالبةً، حينها سيبلَّغ عن استثناء من النوع IllegalArgumentException. تفترض الشيفرة السابقة أن الشرْطين المُسبَقين هما A != 0 وBB-4AC >= 0، وسيبلّغ البرنامج عن استثناء من النوع IllegalArgumentException إذا لم يتحقّق أي من الشرطين السابقين، وعمومًا، عندما يجد برنامجٌ فرعيٌ شرطًا غير صالحٍ فإنّه يبلِّغ عن الاستثناء بصورةٍ مناسبةٍ، فإذا امتلك البرنامج المستدعي للبرنامج الفرعي طريقةً مناسبةً لمعالجة الخطأ، فسيَلتقط الاستثناء ويعالجه، بينما سينهار البرنامج إذا لم يعالَج الاستثناء، وسيكون على المبرمج إصلاح برنامجه. تتضمّن عبارة throw الموجودة تعريف definition البرنامج الفرعي أنواعًا مختلفةً من الاستثناءات، يمكن أن يُفصل بينها بفاصلةٍ منقوطةٍ كالتالي: void processArray(int[] A) throws NullPointerException, ArrayIndexOutOfBoundsException { ... معالجة الأحداث الإجبارية Mandatory Exception Handling وضّحنا في المثال السابق إمكانية تبليغ البرنامج الفرعي root()‎ عن استثناء من النوع IllegalArgumentException، الأمر الذي يُعَد مجرد مجاملةً للقارئين المحتملين للشيفرة، وذلك لأن الاستثناءات من النوع IllegalArgumentException ليست إجباريةً، إذ يمكن لأي برنامجٍ أن يبلّغ عن استثناء من النوع IllegalArgumentException دون أن يعلِن عن ذلك صراحةً، وعليه قد يَلتقط البرنامج المستدعي الاستثناء أو يتجاهله بنفس الكيفية التي يمكن لمبرمجٍ أن يختار التقاط استثناء من النوع NullPointerException أو يتجاهله. يختلف الأمر بالنسبة لأصناف الاستثناءات التي تتطلّب ما يعرَف باسم المعالجة الإجبارية mandatory handling، فإذا أمكن لبرنامجٍ فرعيٍ أن يبلّغ عن استثناء من ذلك النوع، فيجب أن يعلَن عن ذلك صراحةً ضِمن تعريف البرنامج routine definition باستخدام عِبارة throws، حيث يُعَد عدم الإعلان في تلك الحالة خطأً في قواعد بناء الجملة syntax error، ويطلَق عليها اسم الاستثناءات المتحقَّق منها checked exceptions، أي أن المصرِّف سيفحص إذا ما كانت تلك الاستثناءات قد عولجت أم لا. سنفترض أنه من الممكن لتعليمةٍ ضِمن برنامجٍ فرعيٍ أن تولّد استثناءً متحقَّقًا منه، بمعنى أنه استثناء يتطلّب معالجةً إجباريةً، فمثلًا قد تكون تعليمة throw إذا كان التبليغ مباشرًا، أو تعليمة استدعاءٍ لبرنامجٍ فرعيٍ آخرٍ قد أعلَن عن قدرته على التبليغ عن الاستثناء؛ وفي جميع الحالات، يجب أن يعالَج الاستثناء، وهو ما يتحقَّق بطريقةٍ من اثنتين، الأولى عن طريق وضع تلك التعليمة داخل تعليمة try تتضمّن عِبارة catch متلائمةً مع نوع الاستثناء، في تلك الحالة يعالِج البرنامج الفرعي الاستثناء، وبذلك لن يرى أي مستدعي له ذلك الاستثناء أبدًا، بينما يعلِن البرنامج الفرعي عن قدرته على التبليغ عن الاستثناء من خلال إضافة عِبارة throws إلى تعريفه لتنبيه أي مستدعي له من احتمالية حدوث استثناء أثناء الاستدعاء، وبذلك يَلزم ذلك المستدعي الجديد أن يعالِج الاستثناء ضِمن تعليمة try أو أن يعلِن بدوره عن إمكانية حدوث الاستثناء ضِمن تعريفه. تُعَد معالجة الاستثناء إجباريةً لأي صنفٍ غير مشتقٍ من الصنف الفرعي RuntimeException أو الصنف Error، حيث تمثِّل الاستثناءات المتحقّق منها شروطًا لا يتحكّم بها المبرمج مثل قيمةٍ مُدخَلةٍ غير صالحةٍ أو فعلٍ غير صالحٍ من قبل المستخدم، ولأنه لا توجد طريقةٌ لتتجنّب وقوع مثل تلك الأخطاء، يَلزم أي برنامجٍ متينٍ أن يستعد لمعالجتها، ولهذا لا تسمح جافا للمبرمجين بتجاهُل معالجة تلك الأخطاء. تحتوي برامج جافا للدخْل والخرْج على جزءٍ كبيرٍ من الاستثتاء المتحقَّق منها، ويعني ذلك أنه يصعُب استخدام تلك البرامج دون درايةٍ كافيةٍ بمفهوم معالجة الاستثناءات، وسنناقش عمليات الدخْل والخرْج وكيفية استخدام الاستثناءات المتحقَّق منها على بصورةٍ مكثّفةٍ في جزئية لاحقة من هذه السلسلة. البرمجة باستخدام الاستثناءات يساعدك استخدام الاستثناءات على كتابة برامجٍ متينةٍ لأنها توفّر أسلوبًا مُنظمًا ومُهيئًا للتعامل مع المتانة، إذ تحتاج عادةً عندما تكتب برنامجٍ متينٍ إلى كتابة الكثير من تعليمات if، الأمر الذي عادةً ما يشوّش على الشيفرة الأساسية، ولحسن الحظ، تمكّنك الاستثناءات من كتابة تنفيذٍ نظيفٍ يشمل جميع الحالات الطبيعية لخوارزميةٍ معينةٍ، بحيث تعالَج الحالات الاستثنائية بعبارة catch ضِمن تعليمة try. يبلّغ البرنامج عن استثناء عندما يواجه حالةً استثنائيةً ولا يجد طريقةً لمعالجتها، ففي بعض الحالات، يكون من المنطقي التبليغ عن استثناء ينتمي لإحدى أصناف جافا المعرّفة مسبقًا مثل IllegalArgumentException أو IOException، بينما إذا لم يتوفّر صنفٌ قياسيٌ يمثِّل تلك الحالة الاستثنائية تمثيلًا كافيًا، سنعرِّف صنفًا جديدًا بشرْط أن ينتمي للصنف القياسي Throwable أو أيًا من أصنافه الفرعية. فمثلًا، يُشتق الصنف التالي من الصنف Exception، وذلك لأنه يتطلّب معالجةً اجباريةً عند استخدامه: public class ParseError extends Exception { public ParseError(String message) { // ‫أنشيء كائنًا من النوع ParseError بحيث يحتوي على رسالة الخطأ الممرَّرة super(message); } } يبني المُنشِئ constructor المُعرَّف سابقًا بإنشاء كائناتٍ من النوع ParseError، ويرث الصنف البرامج getMessage()‎ وprintStackTrace()‎ من الصنف الأعلى، كما تَستدعي التعليمة super(message)‎ المُنشِئ المُعرَّف في الصنف الأعلى superclass، فإذا كان e كائنًا من النوع ParseError، فسيسترجع استدعاء الدالة e.getMessage()‎ رسالة الخطأ المخصّصة بالمُنشِئ. سنستخدم تعليمة throw التالية للتبليغ عن استثناء من النوع ParseError، إذ يجب أن تمرّر رسالة خطأٍ إلى منشِئ الكائن، كما يلي: throw new ParseError("Encountered an illegal negative number."); أو هكذا: throw new ParseError("The word '" + word + "' is not a valid file name."); يُعَدParseError صنفًا فرعيًا من الصنف Exception، ولذلك فإنه يمثِّل استثناءً متحقَّقًا منه، بمعني أنه إذا لم تقع تعليمة throw المبلّغة عنه ضِمن تعليمة try لالتقاطه، فيجب أن يعلِن البرنامج الفرعي المتضمِّن للتعليمة عن قدرته على التبليغ عن استثناء من النوع ParseError بإضافة عبارة throws ParseError إلى تعريفه، هكذا: void getUserData() throws ParseError { . . . } في المقابل، إذا اشتُق ParseError من الصنف الفرعي RuntimeException بدلًا من الصنف Exception، فلن يكون من الضروري إضافة العبارة السابقة؛ إذ يُعَد الاستثناء ParseError في هذه الحالة استثناءً متحقَّقًا منه. تُستخدم تعليمة try مع عِبارة catch خاصة بالصنف ParseError إذا أراد برنامجٌ معينٌ أن يعالِج استثناء من النوع ParseError، هكذا: try { getUserData(); processUserData(); } catch (ParseError pe) { . . . // Handle the error } تَلتقط عبارةً مثل catch (Exception e)‎ الاستثناءات من النوع ParseError إلى جانب أيّ كائنٍ آخرٍ من النوع Exception، حيث يُعَد ParseError صنفًا فرعيًا من الصنف Exception. يفيد عادةً تخزين بعض البيانات الإضافية ضمن الكائنات الممثِّلة للاستثناءات كالتالي: class ShipDestroyed extends RuntimeException { Ship ship; // Which ship was destroyed. int where_x, where_y; // Location where ship was destroyed. ShipDestroyed(String message, Ship s, int x, int y) { super(message); ship = s; where_x = x; where_y = y; } } يتضمّن كائن ShipDestroyed المعرَّف في المثال السابق رسالة خطأٍ إلى جانب بعض المعلومات الإضافية عن السفينة المدمَّرة، انظر المثال التالي: if ( userShip.isHit() ) throw new ShipDestroyed("You've been hit!", userShip, xPos, yPos); قد لا يمثِّل الشرط المستخدَم خطأً فعليًا، وإنما قد يكون مجرد انحرافٍ عن المسار الطبيعي للعبة، حيث تُستخدم الاستثناءات في بعض الأحيان لتعالِج مثل تلك الانحرافات بدقةٍ. تبرز فائدة التبليغ عن الاستثناءات عندما تكتب توابعًا أو أصنافًا متعددة الأغراض، ضمن أكثر من برنامجٍ واحدٍ، ففي مثل تلك الحالات، قد لا يدرك مبرمج تلك التوابع والأصناف أنسب طريقةٍ لمعالجة خطأٍ معينٍ؛ إذ أنه لا يعرِف الكيفية التي سيستخدِم بها ذلك التابع أو الصنف، ويكتفي المبرمجون المبتدئون في تلك الحالة بطباعة رسالة خطأٍ ثم الاستمرار على نحوٍ طبيعيٍ، الأمر الذي قد يؤدي إلى نتائج غير متوقعةٍ، إذ لا يُعَد طباعة رسالة خطأٍ وإنهاء البرنامج الخيار الأفضل أيضًا؛ وذلك لأنه لا يعطي فرصةً للبرنامج ليعالِج الخطأ. يحتاج البرنامج المستدعي للدالة أو المستخدِم للصنف إلى معرفة أن خطأً معينًا قد حدث بالفعل، وفي اللغات التي لا تدعم الاستثناءات، يكون البديل الوحيد هو إعادة قيمةٍ خاصةٍ أو ضبْط قيمة متغيّرٍ عامٍ global variable ليشير إلى حدوث الخطأ، فمثلًا، تعيد الدالة readMeasurement()‎ قيمة "1-" إذا كانت قيمة مُدخَل المستخدم غير صالحةٍ، ومع ذلك قد لا يَفحص البرنامج القيمة المعادة من الدالة، كما أنه لا يستخدِم القيمة "1-" للإشارة على حدوث خطأٍ من قراءة القياسات السالبة؛ ولهذه الأسباب تُعَدّ الاستثناءات الطريقة الأمثل التي ينبغي للبرامج الفرعية أن تَلجأ إليها عندما تواجه خطأً معينًا. يَسهُل أن نعدّل الدالة readMeasurement()‎ لتبلّغ عن استثناء بدلًا من أن تعيد قيمةً خاصةً تمثِّل حدوث خطأٍ، فقد تبلّغ النسخة المعدّلة من البرنامج الفرعي عن استثناء من النوع ParseError المشتق من الصنف Exception إذا أدخل المستخدم قيمةً غير صالحةٍ، وقد يكون من الأنسب في تلك الحالة أن يبلَّغ عن استثناء من الصنف القياسي IllegalArgumentException بدلًا من اللجوء إلى تعريف صنفٍ جديدٍ، انظر النسخة المعدّلة من الدالة: // [2] static double readMeasurement() throws ParseError { double inches; // حاصل مجموع البوصات الكلية double measurement; // قيمة القياس المُدخَلة String units; // وحدة القياس المُدخَلة char ch; // لفحْص الحرف التالي من مُدخَل المستخدم inches = 0; // No inches have yet been read. skipBlanks(); ch = TextIO.peek(); // [1] while (ch != '\n') { /* Get the next measurement and the units. Before reading anything, make sure that a legal value is there to read. */ if ( ! Character.isDigit(ch) ) { throw new ParseError("Expected to find a number, but found " + ch); } measurement = TextIO.getDouble(); skipBlanks(); if (TextIO.peek() == '\n') { throw new ParseError("Missing unit of measure at end of line."); } units = TextIO.getWord(); units = units.toLowerCase(); /* حوّل قيمة القياس إلى البوصة وأضفها إلى inches */ if (units.equals("inch") || units.equals("inches") || units.equals("in")) { inches += measurement; } else if (units.equals("foot") || units.equals("feet") || units.equals("ft")) { inches += measurement * 12; } else if (units.equals("yard") || units.equals("yards") || units.equals("yd")) { inches += measurement * 36; } else if (units.equals("mile") || units.equals("miles") || units.equals("mi")) { inches += measurement * 12 * 5280; } else { throw new ParseError("\"" + units + "\" is not a legal unit of measure."); } // اختبر إذا ما كان المحرِّف التالي هو محرِّف نهاية السطر أم لا skipBlanks(); ch = TextIO.peek(); } // end while return inches; } // end readMeasurement() [1] إذا كان هناك مُدخَلات أخرى في السطر، اقرأ قيمة القياس وأضف مكافئها من البوصات إلى المتغيّر inches، وإذا وقع خطأٌ أثناء تنفيذ الحلقة، أنهِ البرنامج الفرعي فورًا وأعد القيمة "1-". [2] اقرأ سطرًا واحدًأ من مُدخَلات المستخدِم، بحيث يكون الشرط المُسبَق: السطر المُدخَل غير فارغ، ويكون الشرط اللاحق: إذا كان مُدخَل المستخدم صالحًا، حوّل قيمة القياس إلى وحدة البوصة وأعدها مثل قيمةٍ للدالة، بينما إذا كانت قيمة المُدخَل غير صالحةٍ، ستبلّغ الدالة عن استثناء من النوع ParseError. سنستدعي البرنامج الفرعي المعرَّف في المثال السابق داخل تعليمة try، هكذا: try { inches = readMeasurement(); } catch (ParseError e) { . . . // Handle the error. } يمكنك الاطلاع على شيفرة البرنامج بالكامل في الملف LengthConverter3.java. ترجمة -بتصرّف- للمقال Section 3: Exceptions and try..catch من فصل Chapter 8: Correctness, Robustness, Efficiency من كتاب Introduction to Programming Using Java. اقرأ أيضًا المقال السابق: كيفية كتابة برامج صحيحة باستخدام لغة جافا مقدمة إلى الاستثناءات exceptions ومعالجتها في جافا الاستثناءات Exceptions في Cpp الاستثناءات Exceptions في dot NET
  2. هل أنت من محاربي PHP القدامى وتريد معرفة ما الذي استجد منذ عدِّة سنوات؟ أم أنت منتقلٌ حديثًا إلى PHP من لغةٍ أخرى وتود معرفة الأمور المثيرة في PHP، ها قد وصلت إلى المكان الصحيح. لننفض الغبار عن معلوماتك، وتهيّأ أن تتعلم ميزاتٍ أضيفت حديثًا إلى PHP. معايير PHP-FIG أعداد مشاريع ومكتبات وأطر عمل PHP الموجودة حاليًا مهولة، إذ هنالك العديد من أطر عمل PHP المتوفرة للاستعمال، لكن من الصعب استعمالها معًا، فماذا لو استطاع أحد أطر العمل الاستفادة من مكتبة ما خارجية، بدلًا من كتابتها يدويًا، أو الاستفادة من مكتبة من إطار Laravel مثلًا؟ لهذا الغرض أُنشِئت PHP-FIG (اختصار للعبارة Framework Interoperability Group) في مؤتمر php|tek في 2009ـ التي وضعت عدِّة معايير يُرمَز لها بالاختصار PSR (أي PHP Standards Recommendations) سنورد ذكرها هنا باختصار. PSR-1: معيار كتابة الشيفرات يوضح هذا المعيار الأمور الأساسية التي يجب أخذها بعين الاعتبار عند كتابة الشيفرات لتحقيق أكبر قدر من المحمولية وإمكانية التشغيل مع بقية المكتبات والبرمجيات. يجب استعمال وسوم ‎<?php ?>‎ الاعتيادية، ووسم echo المختصر ‎<?= ?>‎ فقط. يجب أن يكون ترميز ملفات الشيفرات UTF-8 دون BOM. يجب على الملف أن يُنشِئ بنى برمجية جديدة مثل الأصناف (classes) والدوال …إلخ. دون أن يُحدِث أي "تأثير جانبي"، أو يجب أن ينفِّذ الخطوات المنطقية التي تؤدي إلى "تأثيرات جانبية" ولكن لا يُسمَح أن يقوم بكلا الأمرين. نستطيع تعريف "التأثيرات الجانبية" بالأمور غير المتعلقة بتعريف الأصناف أو الدوال، وهي تشمل: توليد المخرجات، أو تضمين الملفات (عبر include أو require)، أو الاتصال إلى الخدمات الخارجية، أو تعديل ضبط ini، أو تعديل المتغيرات العامة أو الكتابة إلى ملفات، وهلم جرًا. يجب أن تكون مجالات الأسماء (namespaces) وأسماء الأصناف متوافقة مع معيار PSR-4 (سنأتي على ذكره بعد قليل)، وهذا يعني أن كل ملف سيحتوي على صنف وحيد باسمه، وفي مجال أسماء من مرحلة (level) واحدة على الأقل. يجب أن تكون أسماء الأصناف على الشكل StudlyCaps (أي الحرف الأول من كل كلمة كبير). يجب أن تكون الثوابت المُعرَّفة في الأصناف بأحرف كبيرة وتفصل الشرطة السفلية بين الكلمات. يجب أن تكون أسماء الدوال على الشكل camelCase (أي الحرف الأول من كل كلمة كبير، عدا أول كلمة). PSR-2: معيار تنسيق الشيفرات يساعد هذا المعيار في تسهيل التعامل مع الملفات التي كتبها مبرمجون آخرون عبر تحديد قواعد مشتركة لكيفية تنسيق شيفرات PHP. يعتمد هذا المعيار على المعيار PSR-1، أي على المبرمج تطبيق قواعد PSR-1 أولًا. يجب أن تُستعمل أربعة فراغات (مسافات) لمحاذاة الشيفرات، وليست مسافات الجدولة (tabs)، ولا يجوز أن تدمج بين الطريقتين، وذلك لتفادي حدوث مشاكل في ملفات الفروقات (diff) وغيرها. ليس هنالك حدٌ أقصى لعدد المحارف في السطر، لكن يستحسن أن يكون بطول 80 محرف أو أقل، ويمكن إضافة أسطر فارغة لتحسين مقروئية النص. ولا يجوز وضع أكثر من تعبير برمجي في السطر الواحد. يجب أن تكون الكلمات المفتاحية (keywords) في PHP بأحرفٍ صغيرة، وكذلك الأمر للثوابت true و false و null. يجب وجود سطر فارغ وحيد بعد تعريف مجال الأسماء (namespace)، وكذلك الأمر بعد use. يجب أن يكون قوس البداية لصنف أو دالة في سطرٍ منفصل، وكذلك قوس النهاية؛ ولا يجوز وضع فراغ بين اسم الدالة وقوس المعاملات، ويجب وضع فراغ وحيد بعد الفاصلة "," في حال وجود أكثر من معامل. يجب تعيين مُحدِّد وصول مثل public أو private أو protected، وعدم استخدام var. يجب وضع فراغ وحيد بعد الكلمة المحجوزة لبنى التحكم (مثل if و for وغيرهما). PSR-3: معيار السجلات يُعرِّف هذا المعيار واجهةً (interface) للتسجيل (logging). PSR-4: معيار التحميل التلقائي شرحنا التحميل التلقائي في درس توزيع شيفرات PHP على عدة ملفات، يمكنك العودة إليه لمزيدٍ من المعلومات. الحزم البرمجية تخيل معي الوضع التالي: أنت مُطور PHP، لديك مشروع تود تطويره، قد تختار إطار عمل مُعين لهذه المهمة، لكنك ستحتاج إلى بضعة مكتبات إضافية للقيام بذلك، تخيل بأنك تود أن يقوم تطبيقك بنشر تحديثات مُعينة على حساب المُستخدم على تويتر، وجدت المكتبة التي ترغب في استخدامها لكنها مكتبة تعتمد على مكتبة أخرى. مُطور PHP من العصر الحجري سيقوم بالتالي: سيقوم بتحميل نُسخة من إطار العمل، ومن ثم يقوم بإنشاء مُجلد يضع فيه المكتبات الإضافية التي يحتاجها ومن ثم يُحاول فهم آلية عملها ليربطها ببعضها البعض. قد تؤتي هذه الطريقة أكلها، وقد تسمح لك بتطوير مشروعك "من دون أية مشاكل"، لكن ماذا يحدث مثلا لو تم إطلاق تحديث لأي من المكتبات التي تعتمد عليها؟ هل ستقوم بإعادة تحميلها من جديد واستبدال الإصدار القديم بالجديد؟ هل يُمكن أن تفعل ذلك لو كنت تستخدم أكثر من مكتبة يعتمد بعضها على بعض؟ لست متأكدا من ذلك. لكن ما هو البديل؟ هل عرفت فائدة أدوات إدارة الحزم؟ ما رأيك أن تكمل قراءة مقال ما هو Composer ولماذا يجب على كل مطور PHP استخدامه وتتعرف على composer وتتعلم طريقة استخدامه. كلمات المرور توجد قابلية تسجيل مستخدمين جدد في أغلبية المواقع، وهذا يعني أنَّ على المستخدم توفير كلمة مرور لكي يدخل على حسابه، لكن هل تساءلت من قبل عن أكثر الطرق أمانًا في تخزين كلمات مرور المستخدمين؟ سأنصحك بعض النصائح في هذا الصدد. لا يجدر بك معرفة كلمات مرور مستخدميك هذا يعني أنَّك ستخزِّن كلمة المرور على شكل نص بسيط في مكانٍ ما في قاعدة بياناتك، افترض مثلًا أنَّ موقعك قد تعرض للاختراق، واستطاع المُخترَق الوصول إلى قاعدة بيانات موقعك واستطاع رؤية جميع كلمات مرور مستخدميك! أنت تعرض سلامة حسابات مستخدميك الأخرى (وسمعة موقعك) للخطر. لا ترسل كلمات المرور الجديدة عبر البريد الإلكتروني هذا خطأٌ كبيرٌ لأنه يعني أنَّ الموقع يعلم كلمة مرور المستخدم، ولقد أرسلها عبر خدمة البريد (قد لا يكون الاتصال مشفرًا!)؛ وإنما عليك إرسال رابط سيسمح للمستخدم بكتابة كلمة مرور جديدة. لا تشفر (encrypt) كلمات المرور قد تظن أنَّ التشفير فكرةٌ جيدةٌ لكن تذكر أنَّ العملية قابلة للعكس، فأي شخص يملك وصولًا إلى شيفرات موقعك البرمجية سيقدر على تحويل كلمات المرور المُشفَّرة إلى أصلها. لا تستخدم MD5 من الجيد أن تستعمل طريقة تحويل غير قابلة للعكس، فهذه الطرق -مثل MD5- غير قابلة للعكس، مما يُصعِّب مهمة معرفة كلمة المرور الأصلية؛ وإذا أردت التأكد أنَّ كلمة المرور التي أدخلها المستخدم مطابقة لكلمة المرور المخزنة في قاعدة البيانات، فعليك أولًا تحويل كلمة المرور التي أدخلها بإحدى الطرق ثم مقارنتها مع الكلمة المخزنة في قاعدة البيانات. لكن يَسهُل كثيرًا توليد جداول بالقيم الأصلية والقيم المحوَّلة تسمى "جداول rainbow"، وسيتم البحث عن القيم المحولة إلى أن يُعثَر على تطابق (ينطبق المثل على SHA-1). أتت PHP 5.5 بدوال بسيطة لتحويل كلمات المرور بأمان وهي password_hash()‎ و password_verify()‎. تُستعمل الدالة الأولى لتحويل كلمة المرور إلى عبارة غير قابلة للعكس التي تستطيع تخزينها بأمان في قاعدة بياناتك. $hash = password_hash($password, PASSWORD_DEFAULT); هذا كل ما في الأمر: أول وسيط هو كلمة المرور التي تريد تحويلها، والوسيط الثاني هو الخوارزمية التي تريد استخدامها للتحويل. الخوارزمية الافتراضية هي bcrypt، ويجب أن تُخزِّن القيم الناتجة في حقل أكبر من 60 محرف في قاعدة البيانات (ربما تستعمل 255). يمكنك أيضًا تمرير PASSWORD_BCRYPT كوسيط في حال أردت أن يكون الناتج بطول 60 محرف دومًا. أما دالة password_verify()‎ فهي تقارن كلمة المرور التي أدخلها المستخدم (الوسيط الأول) بكلمة المرور المحوَّلة والمخزنة في قاعدة البيانات (الوسيط الثاني). <?php if (password_verify($password, $hash)) { // كلمة المرور صحيحة } else { // كلمة المرور غير مطابقة } الأخطاء في PHP أكثر ما يواجهه المبرمج في حياته هو الأخطاء! نحاول -نحن معشر المبرمجين- أن نتلافى حدوث الأخطاء، لكن ذلك ليس ممكنًا في الحياة العملية. علينا أن نعي أنَّ هنالك تصنيفين: الأول هو الأخطاء (errors) التي رأيتها سابقًا خلال هذه السلسلة، التي تُظهِرها الدوال المُضمَّنة في اللغة؛ والثاني هو الاستثناءات (exceptions) التي تظهر في البرامج التي تعتمد على البرمجة غرضية التوجه. تُصنَّف PHP على أنها لا تعتمد كثيرًا على الاستثناءات، وإنما تعتمد على الأخطاء. فلو حاولت مثلًا أن تطبع قيمة متغير غير معرف، فستظهر تنبيهًا لكن PHP ستكمل تفسير السكربت: $ php -a php > echo $foo; Notice: Undefined variable: foo in php shell code on line 1 أما في اللغات الأخرى التي تعتمد اعتمادًا كاملًا على الاستثناء -مثل بايثون- فستختلف النتيجة: $ python print foo Traceback (most recent call last): File “”, line 1, in NameError: name ‘foo’ is not defined التبليغ عن الأخطاء في PHP هنالك مستويات مختلفة من التبليغ عن الأخطاء في PHP، أشهر ثلاثة مستويات هي الأخطاء (errors) والتنبيهات (notices) والتحذيرات (warning). الأخطاء (errors) هي المشاكل التي تظهر في وقت التشغيل (run-time) التي يكون سببها مشاكل في الشيفرة وستسبب توقف التنفيذ. أما التنبيهات فهي رسائل إرشادية التي قد تسبب مشاكل أثناء تنفيذ السكربت، لكن التنفيذ لن يتوقف. أما التحذيرات فهي شبيهة بالأخطاء لكنها لن توقف عمل السكربت. تستطيع تغيير السلوك الافتراضي للتبليغ عن الأخطاء عبر الدالة error_reporting()‎ التي تستطيع تستطيع ضبط مستوى التبليغ عن الأخطاء أثناء التنفيذ بتمرير ثابت من الثوابت المُعرَّفة مسبقًا لمستويات الأخطاء (E_ERRORللأخطاء، E_NOTICE للتنبيهات، E_WARNING للتحذيرات). فلو كنت تريد مشاهدة الأخطاء والتحذيرات لكنك لا تريد التنبيهات، فيمكنك ضبط ذلك كالآتي: <?php error_reporting(E_ERROR | E_WARNING); تستطيع أن تخبر PHP أن تتجاهل الأخطاء في عبارة برمجية معيّنة باستخدام معامل التحكم بالأخطاء @. يمكنك وضع هذا المعامل قبل تعبير برمجي، وسيتم تجاهل أيّة أخطاء يُسبِّبها هذا التعبير. <?php echo @$foo['bar']; المثال السابق سيطبع قيمة ‎$foo['bar']‎ إن كانت موجودةً، لكنه لن يطبع شيئًا إن لم يكن المتغير ‎$foo أو المفتاح 'bar' موجودًا؛ فدون وجود معامل التحكم بالأخطاء، كان سيظهر أحد التنبيهين: PHP Notice: Undefined variable: foo أو PHP Notice: Undefined index: bar. بالطبع يمكنك استخدام العبارة الآتية لتنجب وضع معامل تجاهل الأخطاء: <?php echo isset($foo['bar']) ? $foo['bar'] : ''; الاستثناءات تمثِّل الاستثناءات جزءًا مهمًا من العديد من لغات البرمجة مثل ruby أو java، فأي شيء يحدث بشكل خاطئ في تلك اللغات -مثل فشل طلبية HTTP أو فشل الاتصال بقاعدة البيانات- فسيُرمَى (throw) استثناء يعني أنَّ هنالك خطأٌ ما. لكن PHP نفسها متساهلة جدًا بهذا الأمر، فلو فشل استدعاء الدالة file_get_contents()‎ فستحصل على FALSE وربما رسالة تحذيرية (أي من المستوى E_WARNING)؛ أما أطر العمل الحديثة، فتستعمل الاستثناءات. تبنى آلية معالجة الأخطاء في PHP على وضع التعبيرات البرمجية في كتلة try التي تريد مراقبتها من أجل الأخطاء، فلو حدث استثناءٌ ما داخل كتلة try (تحدث الاستثناءات عند "رميها" [throw]) فسيُلتَقَط باستخدام catch التي تلي كتلة try التي رمت الاستثناء. أنا متأكدٌ أنَّك تتساءل عن معنى ما سبق. ما رأيك أن نأخذ مثالًا توضيحيًا يساعد على الفهم: <?php try { $num = 10; if ($num < 20) { throw new Exception("Exception here!"); } $foo = "bar"; } catch(Exception $exception) { print "Exception!\n"; } ?> دخل مُفسِّر PHP إلى كتلة try وبدأ تنفيذ الشيفرة، وعند وصوله إلى السطر throw new Exception توقف عن تنفيذ كتلة try وانتقل إلى كتلة catch؛ لاحظ أنَّه عندما "يغادر" مُفسِّر PHP كتلة try فلن يعود إليها مطلقًا، أي أنَّ السطر ‎$foo = "bar"‎ لن يُنفَّذ. قد ترى أنَّ كتلة catch معقدة في بادئ الأمر، لكنني سأريك مثلًا يعتمد على أنَّ مفسر PHP سينتقل إلى كتلة catch المناسبة، لكن علي أولًا شرح التعبير (catch(Exception $exception. نُحدِّد في كتلة catch أنَّ الصنف هو Exception لأنَّ على PHP أن تُحدِّد أي كتلة من كتل catch يجب تنفيذها بالبحث عن الصنف الموافق للصنف الذي رُميَ. أو بالأحرى، تُجري PHP عملية instanceof (هل تذكرها من درس البرمجة كائنية التوجه؟) وهذا يعني أنَّ على الاستثناء الذي رُميَ أن يكون من الصنف المُحدَّد أو من صنف مشتق منه (أعلم تمامًا العلم أنَّ ما سبق يبدو معقدًا، لكنه أبسط بكثير مما تظن). لاحظ أنَّ جميع الاستثناءات مشتقة من الصنف Exception الذي يوفِّر وظائف أساسية، وأغلبية الدوال الموجودة في الصنف Exception هي final أي لا يمكن إعادة كتابتها في الأصناف المشتقة. يمكنك مثلًا استدعاء الدالة ‎$exception->getMessage()‎ لمعرفة رسالة الخطأ ("Exception here!‎" في المثال السابق)، أو يمكنك استدعاء getFile()‎ لمعرفة الملف الذي رمى الاستثناء. هذا المثال يوضِّح الشرح السابق: <?php class ExceptFoo extends Exception { } class ExceptBar extends ExceptFoo { } try { $foo = "bar"; throw new ExceptFoo("Baaaaad PHP!"); $bar = "baz"; } catch (ExceptFoo $exception) { echo "Caught ExceptFoo\n"; echo "Message: {$exception->getMessage()}\n"; } catch (ExceptBar $exception) { echo "Caught ExceptBar\n"; echo "Message: {$exception->getMessage()}\n"; } catch (Exception $exception) { echo "Caught Exception\n"; echo "Message: {$exception->getMessage()}\n"; } ?> ناتج السكربت السابق: Caught ExceptFoo Message: Baaaaad PHP! هذا منطقي لأننا رمينا استثناء من الصنف ExceptionFoo، ولهذا ستنتقل PHP إلى كتلة catch. جرب الآن هذه الشيفرة: <?php class ExceptFoo extends Exception { } class ExceptBar extends ExceptFoo { } try { $foo = "bar"; throw new ExceptBar("Baaaaad PHP!"); $bar = "baz"; } catch (ExceptFoo $exception) { echo "Caught ExceptFoo\n"; echo "Message: {$exception->getMessage()}\n"; } catch (ExceptBar $exception) { echo "Caught ExceptBar\n"; echo "Message: {$exception->getMessage()}\n"; } catch (Exception $exception) { echo "Caught Exception\n"; echo "Message: {$exception->getMessage()}\n"; } ?> لماذا ناتج الشيفرة السابقة مماثلة لما قبلها؟ لأنَّ PHP تنتقل إلى أول كتلة catch مُطابِقة لصنف الاستثناء أو أيّة أصناف أب له؛ ولما كان ExceptionBar مشتقٌ من ExceptionFoo، وكانت كتلة catch التابع للصنف ExceptionFoo تأتي قبل ExceptionBar، فستنفَّذ كتلة ExceptionFoo. يمكنك إعادة كتابة الشيفرة كالآتي لتفادي هذه الإشكالية: <?php class ExceptFoo extends Exception { } class ExceptBar extends ExceptFoo { } try { $foo = "bar"; throw new ExceptBar("Baaaaad PHP!"); $bar = "baz"; } catch (ExceptBar $exception) { echo "Caught ExceptBar\n"; echo "Message: {$exception->getMessage()}\n"; } catch (ExceptFoo $exception) { echo "Caught ExceptFoo\n"; echo "Message: {$exception->getMessage()}\n"; } catch (Exception $exception) { echo "Caught Exception\n"; echo "Message: {$exception->getMessage()}\n"; } ?> بقي أمرٌ بسيطٌ تجدر الإشارة إليه ألا وهو كتلة finally الموجودة في إصدار PHP 5.5 وما بعده، التي تضمن لك تنفيذ الشيفرات البرمجية الموجودة فيها دائمًا حتى لو حدث استثناءٌ ما. <?php try { complicatedCode(); } catch (NastyException $e) { handleError($e); } finally { importantCleanup(); } ?> المولدات Generators هذه ميزةٌ جديدةٌ في PHP منذ الإصدار 5.5، وهي تسمح لك باستعمال حلقة foreach للمرور على مجموعة من البيانات دون الحاجة إلى إنشاء مصفوفة في الذاكرة، الأمر الذي قد يؤدي إلى تجاوز حد استهلاك الذاكرة المسموح، أو إلى وقت معالجة كبير… إذ تستطيع كتابة دالة مولدة (generator function) التي تشبه الدوال العادية، إلا أنها بدلًا من إعادة قيمة ما، فهي "تُنتِج" (yield) قيمًا لكي يتم المرور عليها باستعمال الحلقات. مثالٌ بسيطٌ عنها هو دالة range(0, 1000000)‎ التي تولد مصفوفة فيها قيم عددية من الصفر إلى المليون، وستستهلك 100 ميغابايت من الذاكرة العشوائية. ونستطيع بدلًا من ذلك أن نكتب مولدة اسمها xrange()‎ -على سبيل المثال- وستستهلك أقل من 1 كيلوبايت! <?php function xrange($start, $limit) { for ($i = $start; $i <= $limit; $i++) { // استعملنا yield بدلًا من return yield $i; } } // استعملنا المصفوفة المُعادة من استدعاء الدالة range في foreach كالمعتاد foreach (range(1, 9) as $number) { echo "$number "; } echo "\n"; // وكذلك استعملنا نواتج المولدة xrange foreach (xrange(1, 9) as $number) { echo "$number "; } ?> الفرق بين الطريقتين في استهلاك الذاكرة فقط، ولا تأثير لها على سرعة التنفيذ. المصادر راجع صفحة Basic Coding Standard و Coding Style Guide و Logger Interface و Autoloading Standard، وتابع الجديد عبر موقع PHP-FIG. راجع مقالة ما هو Composer ولماذا يجب على كل مطور PHP استخدامه لصاحبها يوغرطة بن علي. لمزيد من المعلومات حول تحويل كلمات المرور، راجع صفحة password_hash و password_verify في دليل PHP، وهذا السؤال على stack overflow. انظر إلى قسم الاستثناءات في PHP: The Right Way، ومقالة Exception handling، وصفحة Exceptions في دليل PHP. تعلم المزيد عن المولدات عبر دليل PHP بصفحتيه Generators overview و Generator syntax.
×
×
  • أضف...