سنلقي في هذا المقال نظرةً سريعةً على خاصيتين في جافا لم نتعرّض لهما من قبل هما التوكيد والتوصيف، وعمومًا يُعَدان من المواضيع المتقدّمة في البرمجة.
التوكيد
يجب أن يتحقَّق الشرْط المسبَق عند نقطةٍ معينةٍ في البرنامج لنضمن أن يستمر في العمل بصورةٍ صحيحةٍ؛ أما إذا كان الشرْط المسبَق غير متحقِّقٍ كأن يعتمد على القيمة المدْخَلة من قِبل المستخدِم، فحينها سنفحص ذلك باستخدام تعليمة if
الشرطية، ويقودنا ذلك إلى السؤال التالي: ماذا إذا لم يتحقَّق الشرْط المسبَق؟ قد نبلِّغ عن استثناء، مما يؤدي إلى إنهاء البرنامج إلا إذا التقطه جزءٌ آخرٌ من البرنامج وعالجه.
يحدُث كثيرًا أن يحاول المبرمِج أن يكْتب برنامجًا بطريقةٍ تضْمن تحقُّق شرطٍ مسبَقٍ معينٍ بدلًا من أن يستخدم تعليمة if
ليَفحص إذا ما تحقَّق الشرط أم لا، ومهما كانت نية المبرمِج، فإن البرنامج قد يحتوي على خطأٍ برمجيٍ bug يُفسد ذلك الشرْط المسبَق، وعليه يظلّ فحْص الشرْط المسبَق فكرةً جيدةً أثناء مرحلة تنقيح الأخطاء debugging على الأقل.
كما يجب أن يتحقَّق الشرْط اللاحق عند نقطةٍ معينةٍ في البرنامج بناءً على الشيفرة التي نفذها الحاسوب قبلها. سنفترض أن الشيفرة كُتبت بطريقةٍ صحيحةٍ، لذا سيتحقَّق الشرْط اللاحق؛ ومع ذلك يُعَد اختبار إذا ما كان الشرْط اللاحق متحققًا أم لا بصورةٍ فعليةٍ فكرةً جيدةً (خصوصًا أثناء مرحلة تنقيح الأخطاء) لفحْص ما إذا كان هناك خطأٌ برمجيٌ قد أفسد ذلك الشرط أم لا؟ وينطبق الأمر نفسه على لامتباينات حلقات التكرار ولامتباينات الأصناف، حيث يجب أن تتحقَّق ضِمن نُقطٍ معينةٍ من البرنامج، أما إذا لم تحقَّق في تلك النقط، فيعني ذلك احتواء البرنامج على خطأٍ برمجيٍ.
امتلكت لغتي C وC++ دومًا طريقةً لإضافة ما يُعرف باسم التوكيدات إلى البرامج، وتُكتب تلك التوكيدات على الصورة assert(condition)
حيث condition عِبارةٌ عن تعبيرٍ expression من النوع المنطقي الذي يمثِّل شرْطًا مسبَقًا أو لاحقًا، وينبغي يتحقَّق ذلك الشرْط عند تلك النقطة من البرنامج، فعندما يقابل الحاسوب توكيدًا أثناء تنفيذه لبرنامجٍ، فسيحصّل قيمة الشرط condition؛ فإذا لم يتحقَّق الشرط، فإنه سينهي البرنامج؛ أما إذ كان الشرط متحققًا، فسيستمر بتنفيذ البرنامج على نحوٍ طبيعيٍ، ويسمح ذلك باختبار إذا ما كانت نية المبرمج بخصوص شرطٍ معينٍ متحقِّقةً أم لا، فإذا لم تكن كذلك، فذلك يعني وجود خطأٍ برمجيٍ في ذلك الجزء من البرنامج الذي يسبق التوكيد، بالإضافة إلى ذلك تمكّنك لغتي C وC++ من تعطيل فحْص تلك التوكيدات أثناء وقت التصريف compile time، بمعنى أنك تستطيع أن تختار تضْمين التوكيدات أو عدم تضْمينها في البرنامج المصرّف، وعادةً ما تصرّف البرامج أثناء مرحلة تنقيح الأخطاء وفقًا للنوع الأول من التصريف compilation، بينما يصرّف إصدار نسخة البرنامج الفعلي مع تعطيل التوكيدات مما يضمن كفاءةً أعلى للبرنامج لأن الحاسوب لن يفحص كلّ تلك التوكيدات.
لا توفّر النسخ الأقدم من جافا خاصية التوكيدات، فمنذ الإصدار 1.4، أضيفت إليها نسخةً مماثلةً لتلك المدعّمة بلغتي C/C++، وبالمثل، يمكنك أن تفعّل توكيدات جافا أثناء مرحلة تنقيح الأخطاء أو أن تعطّلها أثناء التنفيذ العادي للبرنامج، إلا أن ذلك يحدث أثناء وقت التشغيل run time لا وقت التصريف، مما يعني أن التوكيدات تتضمّن دائمًا ملفات الأصناف المصرّفة بحيث يتجاهل الحاسوب تلك التوكيدات ولا يفحص شروطها أثناء التشغيل العادي للبرنامج ولا تؤثّر على أداء البرنامج؛ في المقابل، يمكن تشغيل البرنامج أثناء تفعيل التوكيدات خلال مرحلة تنقيح الأخطاء، وعندها قد تَكتشف تلك التوكيدات بعض الأخطاء البرمجية وتُحدد مواضعها إلى حدٍ كبيرٍ.
تُكتب تعليمات التوكيد assertion statement في جافا بصيغةٍ من اثنتين:
assert condition ;
أو:
assert condition : error-message ;
حيث يُعَد condition عبارةٌ عن تعبيرٍ منطقيٍ boolean-valued expression أما error-message فهي عبارةٌ عن سلسلةٍ نصيةٍ string أو تعبيرٍ من النوع String
.
اقتباسلاحظ أن
assert
كلمةً محجوزةً في لغة جافا، بمعنى أنها لا تستخدَم مثل معرّف هوية identifier، وتتيح جافا أن تكتب تلك التعليمات في أي مكانٍ تُكتب فيه أي تعليمةٍ أخرى.
إذا عطّلت التوكيدات وشغّلت برنامجٍ معينٍ، فستكافِئ عندها التوكيدات التعليمة الفارغة empty statement، ولن يكون لها أي تأثيرٍ؛ أما إذا فعّلت التوكيدات فسيواجه الحاسوب تعليمة توكيدٍ في البرنامج، وسيحصّل قيمة الشرْط condition، وإذا كانت قيمته تساوي true
، فسيستمر في تنفيذ البرنامج بطريقةٍ عاديةٍ، وإذا كانت قيمته تساوي false
، فسيبلِّغ الحاسوب عن استثناء من النوع java.lang.AssertionError
، وينهار البرنامج (إلا إذا التُقط بتعليمة try
)؛ أما إذا تضمّنت تعليمة التوكيد error-message، فستظهر مثل رسالة خطأٍ في الاستثناء AssertionError
المبلَّغ عنه.
يمكننا إذًا أن نقول أن التعليمة الآتية:
assert condition : error-message;
تكافِئ الشيفرة التالية:
if ( condition == false ) throw new AssertionError( error-message );
اقتباسلاحظ أن تعليمة
if
الشرطية تنفّذ دائمًا عند تشغيل البرنامج، بينما لا تنفّذ تعليمات التوكيد assertion statement إلا عند تفعيل التوكيدات عند التشغيل.
ينقلنا ذلك إلى السؤال التالي: متى نستخدم التوكيدات بدلًا من الاستثناءات؟ القاعدة العامة هي استخدام التوكيدات لفحص الشروط التي يجب أن تتحقَّق إذا كُتب البرنامج كتابةً صحيحةً، حيث تساعد التوكيدات على اختبار إذا ما كُتب البرنامج بصورةٍ صحيحةٍ أم لا، كما تساعد في العثور على الأخطاء إن وُجدت، في المقابل، لا تُفحص تلك التوكيدات عند التشغيل العادي للبرنامج أي بعْد اختباره وتنقيحه، ومع ذلك إذا ظهرت مشكلةً لاحقًا، فيمكننا أن نحدّد موضع ذلك الخطأ عن طريق الاستعانة بتلك التوكيدات؛ فمثلًا إذا أخبرك شخصٌ ما بأن البرنامج الخاص بك لا يعمل عندما يفعل كذا وكذا، فتستطيع بسهولةٍ أن تشغّل البرنامج مع تفعيل التوكيدات وتنفّذ كذا وكذا، وقد تفيدك التوكيدات في تحديد موضع البرنامج المتضمّن لذلك الخطأ.
فمثلًا، يحسِب التابع root()
التالي من المقال السابق هو الجذر التربيعي لمعادلةٍ من الدرجة الثانية، فإذا تأكدت من أن البرنامج سيستدعي ذلك التابع بقيم معامِلاتٍ arguments صالحةٍ فقط، سنضيف تعليمات توكيدٍ لا استثناءات كالتالي:
// [1] static public double root( double A, double B, double C ) { assert A != 0 : "Leading coefficient of quadratic equation cannot be zero."; double disc = B*B - 4*A*C; assert disc >= 0 : "Discriminant of quadratic equation cannot be negative."; return (-B + Math.sqrt(disc)) / (2*A); }
[1] يعيد قيمة الجذر الأكبر للمعادلة A*x*x + B*x + C = 0، ويكون الشرْط المسبَق: A != 0 و B*B - 4*A*C >= 0
كما ذكرنا مسبقًا، لا تُفحص التوكيدات عند التشغيل العادي للبرنامج؛ لأنه إذا كان اعتقادك بأن التابع في المثال السابق لا يستدعى أبدًا إلا بمعاملاتٍ صالحةٍ صحيحًا، فإن فحْص الشروط بتلك التوكيدات غير ضروريٍ، أما إذا كان اعتقادك غير صحيحٍ، فينبغي أن تظْهر تلك المشكلة أثناء مرحلة اختبار البرنامج وتنقيح أخطاءه أي عند تشغيل البرنامج مع تفعيل التوكيدات.
إذا كان التابع root()
جزءًا من مكتبةٍ برمجيةٍ تُستخدَم من قِبل مبرمجين آخرين، فسيكون الموقف أقل وضوحًا، حيث يوصي توثيق أوراكل جافا بألا تُستخدم التوكيدات لفحص المواصفات الاصطلاحية contract للتوابع العامة public، فإذا تعدّى مستدعيًا تابعًا على مواصفاته الاصطلاحية بتمرير معاملاتٍ غير صالحةٍ، فينبغي أن يبلِّغ التابع عن استثناءٍ، وسيَفرض تلك المواصفة الاصطلاحية بغض النظر عما إذا ما فُعّلت التوكيدات أم لا، وفي حين يَعتمد مبرمجي جافا على الستثناءات لفرض المواصفة الاصطلاحية الخاصة بتابعٍ معينٍ، يكون استخدام التوكيدات أحيانًا أكثر معقوليةً؛ وقد يرى البعض أن التوكيدات صُمّمت لتساعدك على تنقيح شيفرتك، أما الاستثناءات فقد صُمّمت لتنبيه المبرمجين الآخرين عندما يستخدمون شيفرتك على نحوٍ خاطئٍ.
عمومًا لن يضرّ استخدام توكيدٍ لفحْص شرطٍ لاحقٍ أو لامتباينٍ، فهي في النهاية شروطٌ يتوقّع أن تتحقَّق دائمًا مما إذا كان البرنامج خاليًا من أي أخطاءٍ برمجيةٍ، وعليه يكون من المنطقي استخدام توكيداتٍ لفحْص تلك الشروط أثناء مرحلة التنقيح دون التسبّب بأي ضررٍ على كفاءة البرنامج أثناء التشغيل العادي للبرنامج، فإذا لم يتحقَّق أي من الشرطين اللاحق أو اللامتباين، فسيكون ذلك بمثابة خطأٍ برمجيٍ، وهو أمر يجب أن يُكتشف أثناء مرحلة الاختبار testing وتنقيح الأخطاء.
يجب أن تُفعَّل التوكيدات عند تشغيل البرنامج لتؤثِّر به، حيث تعتمد طريقة التفعيل على البيئة البرمجية programming environment المُستخدمة، ولذلك ينبغي أن تضيف الخيار -enableassertions
إلى أمر جافا المستخدَم لتشغيل البرنامج، وذلك في أي بيئةٍ سطر أوامرٍ command line environment تقليديةٍ، فمثلًا، إذا كان RootFinder
هو الصنف المتضمّن للبرنامج main()
، فإن الأمر التالي:
java -enableassertions RootFinder
سينفِّذ البرنامج مع تفعيل التوكيدات، كما يمكنك أن تستخدم -ea
مثل اختصارٍ للخيار -enableassertions
، بمعنى أنه يمكنك كتابة الأمر السابق على النحو التالي:
java -ea RootFinder
يمكنك كذلك أن تفعّل التوكيدات ضمْن جزءٍ معينٍ من البرنامج، فمثلًا، يفعّل الخيار -ea:class-name
التوكيدات الموجودة في الصنف المخصّص فقط، ولاحظ عدم وجود أي مسافاتٍ بين -ea
و:
وclass-name
، في المقابل، يفعّل الخيار -ea:package-name...
كلّ التوكيدات الموجودة في حزمةٍ package معينةٍ إلى جانب حِزمها الفرعية، كما يفعّل الخيار -ea:...
التوكيدات الواقعة ضِمن الحزمة الافتراضية default package، أي تلك الأصناف التي لم تخصّص لها حزمةٌ معينةٌ بعد، فمثلًا يشغّل الأمر التالي برنامج جافا اسمه MegaPaint
مع تفعيل التوكيدات بأصناف الحزمتين paintutils
وdrawing
:
java -ea:paintutils... -ea:drawing... MegaPaint
إذا كنت تستخدم بيئة تطوير إكلبس Eclipse، سيمكنك أن تخصّص الخيار -ea
من خلال إنشاء إعدادات تشغيل run configurations، حيث ستنقر بزر الفأرة الأيمن على اسم الصنف الرئيسي في نافذة مُستكشِف الحِزم Package Explorer، ثم اختر Run As من القائمة، ثم Run من القائمة الفرعية، وستظهر نافذة ضبط إعدادات التشغيل؛ وستجد أن كلًا من اسم المشروع وصنفه الأساسي مملوئين بالفعل، انقر على نافذة Arguments، ثم اكتب -ea
في الصندوق أسفل VM Arguments، الآن ستُضاف محتويات ذلك الصندوق إلى أمر جافا المستخدَم لتشغيل البرنامج، إذًا تستطيع أن تُدخِل أي خياراتٍ أخرى ضِمن نفس الصندوق بما في ذلك خيارات تفعيل توكيدات أخرى أكثر تعقيدًا مثل "-ea:paintutils…"، وعندما تنقر على زر Run ، ستُطبَّق تلك الخيارات كما ستطبَّق في كلّ مرةٍ تشغّل فيها البرنامج، إلا إذا بدلّت إعدادات التشغيل أو أنشأت واحدةٍ جديدةٍ، ويمكنك أن تنشئ إعدادي تشغيل مختلفين لنفس الصنف، واحدًا مع تفعيل التوكيدات وآخر مع تعطيلها.
التوصيف
يشير مصطلح التوصيف عادةً إلى الملاحظات المضافة أو المكتوبة إلى جانب النص الأساسي للمساعدة على فهمه وإدراكه، وقد يأخذ التوصيف هيئةً ملحوظةً تكتبها لنفسك في هامش كتابٍ أو هامشٍ يضيفه المحرِّر إلى روايةٍ قديمةٍ ليوضّح السياق التاريخي لحدثٍ معينٍ، حيث يُعَد التوصيف بمثابة البيانات الوصفية أو النص الوصفي، بمعنى أنه نصٌ مكتوبٌ عن النص الأساسي وليس جزءًا منه.
تُعَد التعليقات comments مثالًا على التوصيف، ولمّا كان المصرّف يتجاهلها، فهي لن تؤثِّر على معنى البرنامج، إذ أنها تشرح معنى الشيفرة للقارئ فقط، وقد يمكن لأي برنامجٍ آخرٍ باستثناء المصرّف أن يعالج تلك التعليقات؛ وتُعَد تعليقات Javadoc مثالًا على ذلك، حيث تعالَج عبر برنامجٍ ينشِئ منها توثيق واجهة تطوير التطبيقات API documentation، وعمومًا تُعَد التعليقات أحد البيانات الوصفية الكثيرة التي تُضاف للبرامج.
توفّر لغة جافا منذ الإصدار 5.0 ما يُعرف باسم التوصيف، وهي طريقةٌ تسهِّل إنشاء أنواعٍ أخرى جديدةٍ من البيانات الوصفية لبرامج جافا، حيث مكّن التوصيف المبرمجين من اختراع طرقٍ جديدةٍ تمامًا لتوصيف برامجهم وكذلك من كتابة برامج يمكنها قراءة تلك التوصيفات واستخدامها.
لا يؤثر التوصيف على البرامج الموصّفة بصورةٍ مباشرةٍ، ومع ذلك فإنها تُستخدم بكثرةٍ، فمثلًا تصرّح بعض أنواع التوصيف عن نية المبرمج بصورةٍ أكثر وضوحًا، حيث قد يستخدم المصرّف تلك التوصيفات المضافة ليتأكّد من توافق الشيفرة مع نية المبرمج، فقد يُستخدم التوصيف القياسي @Override
ضِمن تعريف definition تابعٍ ليبيّن أنه يعيد تعريف override تابعٍ آخرٍ له نفس بصمة signature معرّفٍ ضِمن إحدى الأصناف العليا superclass؛ حيث يستطيع المصرّف أن يتحقَّق من وجود ذلك التابع المستبدَل، فإذا لم يكن موجودًا فإنه يبلِّغ المبرمِج، ويَعُد التوصيف في تلك الحالة مثل أداةٍ تساعد على كتابة برامجٍ صحيحةٍ؛ لأنها تحذّر المبرمج من وجود خطأٍ محتملٍ بدلًا من تركه يبحث عنه بعد ذلك في هيئة خطأٍ برمجيٍ.
يمكنك أن تكتب @Override
في مقدمة تعريف تابعٍ لإضافة ذلك التوصيف إليه، ويعرّف التوصيف صياغيًا بأنه عِبارة عن مبدِّلٍ modifiers مثل المبدّلات public
وfinal
. انظر المثال التالي:
@Override public void WindowClosed(WindowEvent evt) { ... }
إذا لم يكن هناك تابعٌ اسمه WindowClosed(WindowEvent)
في أي صنفٍ أعلى، فسيبلّغ المصرّف عن وجود خطأٍ، وعمومًا يعتمد هذا المثال على خطأٍ برمجيٍ واجهه الكاتب عندما أراد أن يعيد تعريف التابع windowClosed
بتابعٍ آخرٍ اسمه WindowClosed
، فإذا كان التوصيف @Override
متاحًا في لغة جافا في ذلك الوقت (واستخدمه الكاتب)، كان المصرّف سيرفض الشيفرة وبذلك يوفّر عليه الكثير من الوقت.
تُعَد التوصيفات خاصيةً متقدمةً نوعًا ما، وكان من الممكن عدم ذكرها باستثناء @Override
التي ستراها ضِمن الشيفرة المولّدة تلقائيًا عبر إكلبس وبعض بيئات التطوير المتكاملة integrated development environments الأخرى.
سنذكر كذلك توصيفين قياسيين آخرين هما @Deprecated
و@SurpressWarnings
، حيث يُستخدم الأول لوضع علامة على كل من الأصناف والتوابع والمتغيّرات المتروكة deprecated (أي تلك العناصر المهجورة من اللغة، والتي ما تزال متاحةً فقط لأغراض التوافق مع الشيفرات القديمة)، حيث يولّد المصرّف رسائل تحذيرٍ warning messages عند استخدام أي عنصرٍ متروكٍ، أما التوصيف الثاني فيُستخدم لتعطيل رسائل التحذير التي يولّدها المصرّف.
اقتباسلاحظ أن التوصيف
@SurpressWarnings
يَستقبل معامِلًا parameter يمثِّل نوع رسائل التحذير المطلوب تعطيلها؛ وسنضيفه إلى صنفٍ أو تابعٍ كالتالي:
@SuppressWarnings("deprecation")
وعليه، لن يُصدر المصرّف أي رسائل تحذيرٍ عند استخدام أي عنصرٍ متروكٍ، وهناك أنواعٌ أخرى من رسائل التحذير التي يمكن تعطيلها، إلا أن أسمائها غير قياسيةٍ وتختلف من مصرّفٍ لآخرٍ.
اقتباسملحوظة: تختلف قواعد صياغة معاملات التوصيف annotation parameters (خصوصًا عند وجود عِدّة معامِلات) عن قواعد صياغة معامِلات التوابع العادية.
بالإضافة إلى ما سبق، يمكنك أن تعرّف توصيفًا جديدًا وتستخدمه ضِمن الشيفرة الخاصة بك، في حين ستتجاهل المصرّفات والأداوت البرمجية القياسية تلك التوصيفات، ويمكنك أن تكتب برامجًا قادرةٌ على فهمها وفحْص وجودها ضِمن الشيفرة، كما يمكنك أن تنشئ توصيفٍ يعمل أثناء وقت التنفيذ، مما سيمكن البرنامج من فحْص وجود التوصيف ضِمن الشيفرة المصرّفة الجاري تنفيذها، واتخاذ قرارات بناءً على وجودها أو على قيم معاملاتها.
تساعد التوصيفات المبرمجين على كتابة برامجٍ صحيحةٍ، فيمكنها أن تساعد على إنشاء شيفرةٍ متداولةٍ boilerplate أي شيفرةٌ لها هيئةٌ قياسيةٌ تولَّد بصورةٍ تلقائيةٍ، وعادةً ما تعتمد الشيفرة المتداولة على شيفرةٍ أخرى؛ مثل الشيفرة المستخدمة لحفظ جوانبٍ معينةٍ من حالة البرنامج في ملفٍ ثم استعادتها لاحقًا، وتُعَد الشيفرة المسئولة عن كتابة قيم المتغيّرات المتعلِّقة بحالة البرنامج وقرائتها عمليةً متكرّرةً، ولذلك، يجب أن تُكتب بصورةٍ يدويةٍ، فقد يستخدم المبرمج توصيفًا لوضع علامةٍ على المتْغيّرات التي تُعَد جزءًا من الحالة المطلوب حفظها بحيث يفْحص برنامجًا آخرًا وجود تلك التوصيفات وبناءً على ذلك، يمكنه أن يولِّد شيفرة لحفظ تلك المتغيّرات واستعادتها؛ وقد يكون ذلك ممكنًا حتى بدون تلك الشيفرة إذا كان البرنامج يفْحص وجود تلك التوصيفات أثناء وقت التشغيل ويقرّر على أساس ذلك المتْغيّرات المطلوب تخزينها أو استعادتها.
ترجمة -بتصرّف- للقسم Section 4: Assertions and Annotations من فصل Chapter 8: Correctness, Robustness, Efficiency من كتاب Introduction to Programming Using Java.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.