أنواع الأخطاء وأدوات التشخيص
نتعرّف على أخطاء الصياغة والتنفيذ والمنطق، ونكشفها بعبارات الطباعة ونقاط التوقف في NetBeans.
- تميّز بين أخطاء الصياغة وأخطاء زمن التنفيذ والأخطاء المنطقية.
- تتعرّف على أشهر أسباب كل نوع.
- تستخدم عبارات الطباعة لتتبّع قيم المتغيرات.
- تستخدم نقاط التوقف والتنفيذ خطوة بخطوة في NetBeans.
١الأخطاء جزء طبيعي من البرمجة
لا يوجد مبرمج لا يخطئ. الفرق بين المبتدئ والمحترف أن المحترف يعرف نوع الخطأ بسرعة ويعرف أين يبحث عنه. يُسمّى الخطأ في البرنامج Bug، وتُسمّى عملية البحث عنه وإصلاحه Debugging أي التصحيح.
تنقسم أخطاء البرامج إلى ثلاثة أنواع رئيسية:
| النوع | متى يُكتشف؟ | من يكتشفه؟ | العلامة |
|---|---|---|---|
| أخطاء الصياغة Syntax Errors | قبل التشغيل، أثناء الترجمة | المترجم compiler | خط أحمر في NetBeans، ولا يعمل البرنامج |
| أخطاء زمن التنفيذ Runtime Errors | أثناء التشغيل | آلة جافا JVM | يتوقف البرنامج فجأة برسالة Exception |
| الأخطاء المنطقية Logic Errors | بعد التشغيل، عند مراجعة النتائج | المبرمج نفسه | يعمل البرنامج لكن النتيجة خاطئة |
٢أخطاء الصياغة Syntax Errors
تحدث عندما نخالف قواعد كتابة اللغة، كما يخطئ الطالب في الإملاء أو النحو. المترجم يرفض ترجمة البرنامج، فلا يعمل أبدًا حتى نصلح الخطأ. وتظهر في NetBeans بخط أحمر متعرّج وعلامة ❗ بجانب رقم السطر.
١. نسيان الفاصلة المنقوطة
public class Main { public static void main(String[] args) { int x = 10 System.out.println(x); }}
٢. متغير غير معرّف أو مكتوب بشكل خاطئ
public class Main { public static void main(String[] args) { int total = 5; System.out.println(totl); }}
٣. عدم توافق الأنواع
public class Main { public static void main(String[] args) { int n = "ten"; }}
٣أخطاء زمن التنفيذ Runtime Errors
البرنامج صحيح الصياغة ويبدأ العمل، لكنه يواجه موقفًا لا يستطيع التعامل معه فيتوقف فجأة. تسمّى هذه الأخطاء في جافا استثناءات Exceptions. لاحظ أن الأسطر قبل الخطأ تُنفَّذ، وما بعده لا يُنفَّذ.
١. القسمة على صفر
public class Main { public static void main(String[] args) { int a = 10, b = 0; System.out.println("قبل القسمة"); System.out.println(a / b); System.out.println("بعد القسمة"); }}
٢. إدخال من نوع غير متوقع
البرنامج ينتظر رقمًا صحيحًا من nextInt()، لكن المستخدم كتب كلمة:
import java.util.Scanner;public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.println("ادخل عمرك:"); int age = in.nextInt(); System.out.println("عمرك " + age); }}
سنتعلم في الدرس 15 قراءة رسالة الاستثناء سطرًا سطرًا لمعرفة نوع الخطأ ورقم السطر الذي حدث فيه.
٤الأخطاء المنطقية Logic Errors
أخطرها وأصعبها: البرنامج يُترجَم ويعمل دون أي رسالة خطأ، لكن النتيجة خاطئة. المترجم لا يعرف ماذا تقصد، بل ينفذ ما كتبته حرفيًّا.
١. نسيان الأقواس في حساب المتوسط
int a = 80, b = 90;double avg = a + b / 2;System.out.println(avg); // 125.0 ✗
int a = 80, b = 90;double avg = (a + b) / 2.0;System.out.println(avg); // 85.0 ✓
القسمة تُنفَّذ قبل الجمع، فيصبح الحساب 80 + 45 = 125. الأقواس تفرض الجمع أولًا.
٢. شرط حدّه خاطئ
int grade = 60;if (grade > 60) System.out.println("ناجح");else System.out.println("راسب"); // راسب ✗
int grade = 60;if (grade >= 60) System.out.println("ناجح"); // ناجح ✓else System.out.println("راسب");
الطالب الحاصل على 60 ناجح، لكن الشرط > يستبعده. الخطأ يظهر فقط عند القيمة الحدّية 60.
٣. حلقة تنقص مرة أو تزيد مرة (Off-by-one)
int sum = 0;for (int i = 1; i < 5; i++) sum = sum + i;System.out.println(sum); // 10 ✗
int sum = 0;for (int i = 1; i <= 5; i++) sum = sum + i;System.out.println(sum); // 15 ✓
المطلوب جمع الأعداد من 1 إلى 5، لكن الشرط i < 5 يتوقف عند 4.
٥أداة التشخيص ١: عبارات الطباعة
أبسط أداة وأسرعها: نضيف أوامر طباعة مؤقتة تعرض قيم المتغيرات في نقاط مهمة، فنرى ماذا يحدث داخل البرنامج فعلًا. نبدأ الرسالة بكلمة مميزة مثل DEBUG لنعرف أنها للتشخيص فقط.
int sum = 0;for (int i = 1; i < 5; i++) { sum = sum + i; System.out.println("DEBUG: i=" + i + " sum=" + sum);}System.out.println("المجموع = " + sum);
من الطباعة نرى فورًا أن الحلقة توقفت عند i=4 ولم تصل إلى 5. إذن الخطأ في شرط الحلقة. بعد الإصلاح نحذف أسطر التشخيص.
٦أداة التشخيص ٢: نقاط التوقف Breakpoints
المنقّح (Debugger) في NetBeans يسمح لك بإيقاف البرنامج عند سطر محدد، ثم تنفيذه سطرًا سطرًا ومراقبة قيم المتغيرات دون إضافة أي أوامر طباعة.
| الخطوة | الطريقة في NetBeans |
|---|---|
| ١. ضع نقطة توقف | انقر على رقم السطر في الهامش الأيسر، فيظهر مربع أحمر 🔴 ويتلون السطر |
| ٢. ابدأ التنقيح | Debug ← Debug Project، أو Ctrl + F5 |
| ٣. نفّذ سطرًا واحدًا | Step Over أو F8 |
| ٤. ادخل إلى داخل دالة | Step Into أو F7 |
| ٥. تابع حتى نقطة التوقف التالية | Continue أو F5 |
| ٦. راقب المتغيرات | نافذة Variables أسفل الشاشة تعرض كل متغير وقيمته الحالية |
| ٧. أنهِ التنقيح | Finish Debugger Session أو Shift + F5 |
جرّب محاكاة المنقّح
اضغط «التالي» كأنك تضغط F8 في NetBeans، وراقب نافذة المتغيرات:
int price = 200;int qty = 3;int total = price * qty;if (total > 500) total = total - 50;System.out.println(total);
توقّع وتحقّق
برنامج لا يعمل أصلًا ويظهر فيه خط أحمر في NetBeans. ما نوع الخطأ؟
برنامج يعمل ويطبع 125 بدل 85. ما نوع الخطأ؟
ما الذي يسبب ArithmeticException؟
أي مفتاح في NetBeans ينفّذ السطر الحالي فقط أثناء التنقيح؟
الخلاصة
- خطأ الصياغة يمنع الترجمة، ويظهر بخط أحمر في NetBeans.
- خطأ زمن التنفيذ يوقف البرنامج أثناء العمل برسالة Exception.
- الخطأ المنطقي لا يُظهر رسالة، لكن النتيجة خاطئة.
- عبارات الطباعة المؤقتة تكشف قيم المتغيرات داخل البرنامج.
- نقاط التوقف مع F8 تنفذ البرنامج سطرًا سطرًا وتعرض المتغيرات.
تحدٍّ برمجي: أصلح البرنامج
متوسطالبرنامج التالي يحسب متوسط ثلاث درجات ويحكم بالنجاح إذا كان المتوسط 60 فأكثر، لكن فيه خطآن صياغيان وخطآن منطقيان.
- أصلح أخطاء الصياغة حتى يعمل البرنامج.
- استخدم عبارات الطباعة أو نقاط التوقف لاكتشاف الخطأين المنطقيين.
- مع الدرجات 60 و 60 و 60 يجب أن يطبع: المتوسط = 60.0 ثم ناجح.
ابدأ من هذا الكود، واضغط ▶ شغّل هنا لتكتب حلّك وتجرّبه مباشرة:
public class Main { public static void main(String[] args) { int a = 60, b = 60, c = 60 double avg = a + b + c / 3.0; System.out.println("المتوسط = " + avg); if (avg > 60) System.out.println("ناجح"); else system.out.println("راسب"); }}
💡 تلميح 1
💡 تلميح 2
حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
public class Main { public static void main(String[] args) { int a = 60, b = 60, c = 60; double avg = (a + b + c) / 3.0; System.out.println("المتوسط = " + avg); if (avg >= 60) System.out.println("ناجح"); else System.out.println("راسب"); }}