الأخطاء المنطقية

برنامجي لا يعمل
يصعب العثور على الأخطاء المنطقية أكثر لأن المجمع ونظام التشغيل (run-time system) لا يوفران أي معلومات عن الخطأ. أنت فقط تعرف ما يفترض بالبرنامج أن يفعله، وتعرف أنه لا يفعل ذلك.
الخطوة الأولى هي إيجاد صلة بين الشفرة وبين السلوك الذي تراه. تحتاج إلى فرضية عما يحدث في البرنامج فعلاً.
إليك بعض الأسئلة لتطرحها على نفسك:
· أيوجد شيء يفترض من البرنامج عمله، لكن لا يبدو أنه يحدث؟ ابحث عن الجزء من الشفرة الذي ينفذ تلك الوظيفة وتأكد أنه يشتغل بصورة مناسبة. انظر "مجرى التنفيذ" ، أعلاه.
· هل يحدث شيء لا يفترض أن يحدث؟ ابحث عن أية شفرة في البرنامج تجري وظيفة ما في الوقت الذي لا يجب حدوث ذلك فيه.
· هل يولد أحد أجزاء الشفرة أثراً غير المتوقع؟ تأكد من فهمك للشفرة، خصوصاً إذا كانت تستدعي عمليات جاهزة في Java. اقرأ وثائق هذه العمليات، وجربها باستخدام حالات اختبار بسيطة. قد لا تنفذ هذه العمليات ما تعتقد أنها تنفذه.
حتى تبرمج، تحتاج إلى نموذج عقلي (mental model) لما تجريه الشفرة التي تكتبها. إذا لم تعمل الشفرة كما هو متوقع، فقد لا تكون المشكلة في البرنامج؛ يمكن أن تكون المشكلة عندك.
أفضل طريقة لتقويم نموذجك العقلي تكون بتقسيم البرنامج إلى مكونات (عادة الأصناف والعمليات) واختبارها على انفراد.
إليك أكثر الأخطاء المنطقية شيوعاً لتتحقق منها:
· تذكر أن القسمة الصحيحة تقرب النتائج إلى الأدنى دائماً. إذا أردت الحصول على كسور، استخدم الأعداد العشرية doubles.
· الأعداد العشرية تقريبية دائماً، لذا لا تعتمد عليها للحصول على الدقة الكاملة.
· بصورة أكثر شمولية، استخدم الأعداد الصحيحة للأشياء المعدودة والأعداد العشرية للأشياء القبلة للقياس.
· إذا استخدمت عامل الإسناد، =، بدلاً من عامل المقارنة، ==، في إحدى الجمل الشرطية لتعليمات if، while، أو for، فقد تحصل على عبارة صحيحة لغوياً لكنها تعطي دلالة خاطئة.
· عندما تطبق عامل المقارنة، ==، على كائن، فسوف يتحقق من المطابقة. إذا قصدت التحقق من المساواة، عليك استخدام عملية equals.
· بالنسبة للأنواع المعرّفة بوساطة المستخدم، تتحقق equals من المطابقة. إذا أردت استخدام مفهوم آخر للتكافؤ، عليك كتابة عملية أخرى بنفس الاسم تهيمن على القديمة (override it).
· يمكن أن تؤدي الوراثة إلى أخطاء منطقية خبيثة، بسبب إمكانية تشغيل شفرة موروثة دون الانتباه لذلك. انظر "مجرى التنفيذ"، أعلاه.

لدي عبارة كبيرة غليظة ولا تنفذ ما أريده

كتابة العبارات المعقدة أمر محمود طالما أنها مقروءة، لكنها قد تكون صعبة التنقيح. من الجيد عادة تقسيم العبارات المعقدة إلى سلسلة من الجمل بالاستعانة بمتغيرات مؤقتة.
مثلاً:
كود:
rect.setLocation(rect.getLocation().translate(
-rect.getWidth(), -rect.getHeight()));
يمكن إعادة كتابتها كما يلي
int dx = -rect.getWidth();
int dy = -rect.getHeight();         
Point location = rect.getLocation();
Point newLocation = location.translate(dx, dy);
rect.setLocation(newLocation);
النسخة المفصلة أسهل للقراءة، لأن أسماء المتغيرات توفر معلومات إضافية، وأسهل عند تصحيح الأخطاء، بسبب القدرة على التحقق من أنواع المتغيرات المؤقتة وطباعة قيمها.
من مشاكل العبارات الكبيرة الأخرى هي أن ترتيب الحساب قد لا يكون موافقاً لما تتوقعه. مثلاً، لحساب ، قد تكتب
كود:
double y = x / 2 * Math.PI;
هذا ليس صحيحاً، لأن أولوية الضرب والقسمة متساوية، وسيتم تنفيذ العمليتين بحسب ترتيب ورودها من اليسار إلى اليمين. هذه العبارة تحسب .
إذا لم تكن واثقاً من ترتيب العمليات، استعمل الأقواس لتوضيحه أكثر.
كود:
double y = x / (2 * Math.PI);
هذه النسخة صحيحة، وأسهل للقراءة لمن لم يحفظ ترتيب العمليات.

عمليتي لا تعيد النتيجة المطلوبة
إذا كانت تعليمة العودة ذات عبارة معقدة، فلن تجد فرصة لطباعة قيمة ما للتحقق منها قبل إنهاء العملية. يمكنك استخدام المتغيرات المؤقتة هنا أيضاً. مثلاً، بدلاً من
كود:
public Rectangle intersection(Rectangle a, Rectangle b) {
return new Rectangle(
Math.min(a.x, b.x),
Math.min(a.y, b.y),
Math.max(a.x+a.width, b.x+b.width)-Math.min(a.x, b.x)
Math.max(a.y+a.height, b.y+b.height)-Math.min(a.y, b.y) );
}
يمكنك كتابة
كود:
public Rectangle intersection(Rectangle a, Rectangle b) {
int x1 = Math.min(a.x, b.x);
int y2 = Math.min(a.y, b.y);
int x2 = Math.max(a.x+a.width, b.x+b.width);
int y2 = Math.max(a.y+a.height, b.y+b.height);
Rectangle rect = new Rectangle(x1, y1, x2-x1, y2-y1);
return rect;
}
الآن لديك فرصة لعرض أي من المتغيرات الوسيطة قبل الخروج من العملية. وبإعادة استخدام x1 وy1، أصبحت الشفرة أصغر أيضاً.

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

أنا عالق فعلاً، وأحتاج إلى المساعدة حقاً

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

لا، أنا أحتاج المساعدة بحق
ذلك يحصل. حتى أمهر المبرمجين يعلقون. أحياناً تحتاج إلى عينين جديدتين.
قبل أن تدعو أحداً آخر لمساعدتك، تأكد من تجريب كل الأساليب الواردة أعلاه. يجب أن يكون برنامجك بسيطاً بقدر المستطاع، وعليك أن تعمل مع أبسط الحالات التي تسبب الخطأ. يجب أن تكتب تعليمات طباعة في الأماكن المناسبة (ويجب أن يكون الخرج الذي تولده مفهوماً). عليك أن تفهم المشكلة بشكل جيد حتى تصفها بدقة.
عندما تطلب أحداً ليساعدك، أعطه المعلومات التي يحتاجها.
· ما نوع الخطأ البرمجي؟ نحوي، منطقي أم خطأ تشغيل؟
· ما هو آخر شيء عملته قبل حدوث هذا الخطأ؟ ما هي أسطر الشفرة الأخيرة التي كتبتها، أو ما هي حالة الاختبار الجديدة التي فشل البرنامج فيها؟
· إذا حدثت المشكلة أثناء تجميع البرنامج أو تشغيله، ما هي رسالة الخطأ، وإلى أي جزء من البرنامج تشير؟
· ماذا جرّبت لحل المشكلة، وماذا تعلمت؟
قد ترى الحل أثناء شرحك للمشكلة إلى شخص آخر. هذه الظاهرة شائعة لدرجة أن بعض الناس ينصح بأسلوب لتصحيح الأخطاء يتم بالاستعانة ببطة مطاطية وهو يدعى بالإنكليزية "rubber ducking". وهذه الطريقة تتم كما يلي:
1. اشتر بطة مطاطية من النوع العادي.
2. عندما تواجه مشكلة حقيقية فعلاً، ضع البطة على المكتب أمامك وقل لها، "أيتها البطة، أنا عالق فعلاً في مشكلة. إليكِ ما يحدث معي..."
3. اشرح المشكلة للبطة.
4. استنتج الحل.
5. اشكر البطة المطاطية.
أنا لا أمزح. انظر http://en.wikipedia.org/wiki/Rubber_duck_debugging.

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