📘 شرح الدرس الوحدة 6: الأخطاء والاستثناءات · الدرس 16 من 17
الدرس 16
📋 في الخطة: الوحدتان 11 و 12: رسائل الأخطاء وتصحيحها (+ try/catch) · ⏱ 1 نظري + 3 عملي

رسائل الأخطاء والاستثناءات والوقاية منها

نقرأ رسالة الخطأ كخريطة تدلّنا على مكانه، ونعالج الاستثناءات بـ try و catch، ونصحّح بمنهجية، ونتبع ممارسات تمنع الأخطاء.

trymyNumbers[10] ! catch (Exception e)
بعد هذا الدرس ستستطيع أن:
  • تحدد رقم السطر ونوع الخطأ ووصفه من رسالة المترجم.
  • تقرأ رسالة الاستثناء وتتبّع المكدّس Stack Trace.
  • تعالج الاستثناءات باستخدام try و catch و finally.
  • تتبع منهجية من خمس خطوات لتصحيح الأخطاء وتعيد الاختبار.
  • تطبّق ممارسات برمجة جيدة تقلل حدوث الأخطاء.

١رسالة الخطأ صديقك

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

٢تشريح رسالة خطأ الصياغة

رسائل المترجم في NetBeans تأتي بهذا الشكل الثابت:

Main.javaاسم الملف:4:رقم السطر error:نوعها: خطأ ';' expectedالوصف: المتوقع فاصلة منقوطة
Main.java
public class Main {    public static void main(String[] args) {        int count = 3;        String msg = "عدد الطلاب " + count        System.out.println(msg);    }}
Output – JavaApplication (run)×
Main.java:4: error: ';' expected
String msg = "عدد الطلاب " + count
^
1 error
BUILD FAILED (total time: 0 seconds)

السطر الثاني من الرسالة يعرض السطر نفسه، والعلامة ^ تشير إلى المكان الذي توقف فيه المترجم، وهو هنا نهاية السطر حيث كان ينتظر ;.

٣قاموس أشهر رسائل المترجم

الرسالةمعناهاالحل المعتاد
';' expectedتتوقع فاصلة منقوطةأضف ; في نهاية الجملة
cannot find symbolاسم غير معروف: متغير أو دالة أو كلاستحقق من الإملاء وحالة الأحرف، وأن المتغير معرّف قبل استخدامه
incompatible types: String cannot be converted to intوضع قيمة من نوع في متغير من نوع آخرغيّر نوع المتغير أو حوّل القيمة
missing return statementدالة تعيد قيمة لكن لا يوجد returnأضف return بالقيمة المناسبة
variable k might not have been initializedاستخدام متغير قبل إعطائه قيمةأعطه قيمة ابتدائية مثل int k = 0;
reached end of file while parsingقوس { غير مغلقأضف } الناقصة، واستخدم Alt+Shift+F لتنسيق الكود
')' expectedقوس ) ناقصأكمل الأقواس في الشرط أو الاستدعاء
class, interface, enum, or record expectedكود خارج الكلاس، غالبًا بسبب } زائدةاحذف القوس الزائد

مثال: رسالتان في برنامج واحد

Main.java
public class Main {    static int square(int x) {        int r = x * x;    }    public static void main(String[] args) {        int k;        System.out.println(k);    }}
Output – JavaApplication (run)×
Main.java:4: error: missing return statement
}
^
Main.java:7: error: variable k might not have been initialized
System.out.println(k);
^
2 errors
BUILD FAILED (total time: 0 seconds)

الحل: أضف return r; قبل نهاية الدالة square، واكتب int k = 0;

٤تشريح رسالة خطأ زمن التنفيذ

رسالة الاستثناء أطول، لكنها منظمة. اقرأها من الأعلى:

Exception in thread "main"حدث استثناء في البرنامج الرئيسي java.lang.ArithmeticExceptionنوع الاستثناء: / by zeroالتفاصيل

بعدها تأتي أسطر تبدأ بـ at تُسمّى تتبّع المكدّس Stack Trace: تبيّن سلسلة الدوال التي كانت تعمل لحظة الخطأ. السطر الأول منها هو مكان حدوث الخطأ، والأسطر التالية هي من استدعى هذه الدالة.

Main.java
public class Main {    static double average(int total, int count) {        return total / count;    }    public static void main(String[] args) {        System.out.println("بداية البرنامج");        double a = average(250, 0);        System.out.println(a);    }}
Output – JavaApplication (run)×
run:
بداية البرنامج
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Main.average(Main.java:3)
at Main.main(Main.java:7)
BUILD FAILED (total time: 0 seconds)

نقرأ: الخطأ قسمة على صفر، حدث في الدالة average في السطر 3، والتي استُدعيت من main في السطر 7. إذن نصلح إما الدالة (نتحقق أن count ليس صفرًا)، أو الاستدعاء (لا نمرّر صفرًا).

عندما يبدأ التتبّع من مكتبة جافا

أحيانًا تظهر أولًا أسطر من داخل مكتبات جافا مثل java.base/java.util.Scanner. هذه ليست أخطاء في جافا، بل في طريقة استخدامنا لها. تجاوزها وابحث عن أول سطر فيه اسم ملفك مثل Main.java.

Output – JavaApplication (run)×
run:
ادخل عمرك:
عشرون
Exception in thread "main" java.util.InputMismatchException
at java.base/java.util.Scanner.throwFor(Scanner.java:947)
at java.base/java.util.Scanner.next(Scanner.java:1602)
at java.base/java.util.Scanner.nextInt(Scanner.java:2267)
at java.base/java.util.Scanner.nextInt(Scanner.java:2221)
at Main.main(Main.java:6)
BUILD FAILED (total time: 0 seconds)

السطر المهم هو الأخير: Main.main(Main.java:6)، أي سطر nextInt() في برنامجنا.

٥قاموس أشهر الاستثناءات

الاستثناءمتى يحدث؟مثال
ArithmeticException: / by zeroقسمة عدد صحيح على صفر10 / 0
InputMismatchExceptionإدخال لا يطابق نوع دالة القراءةكتابة «abc» عند nextInt()
NumberFormatExceptionتحويل نص لا يمثل رقمًاInteger.parseInt("12a")
NullPointerExceptionاستخدام كائن لم يُنشأ بعد (قيمته null)استدعاء دالة من متغير قيمته null
ArrayIndexOutOfBoundsExceptionرقم خانة خارج حدود المصفوفةa[3] ومصفوفة طولها 3
StringIndexOutOfBoundsExceptionرقم حرف خارج طول النص"Ali".charAt(5)

٦أقسام الاستثناءات

ThrowableErrorExceptionRuntime ExceptionsOther Exceptions أخطاء خارجية لا علاقةللبرنامج بهاUncheckedChecked
Throwable هو الأساس لجميع الأخطاء. و Exception هو الأساس لجميع الاستثناءات: ما يرث RuntimeException غير متحقق Unchecked، والباقي متحقق Checked.

١) Checked Exception: الاستثناء المتحقق

يتحقق منه المترجِم ويُلزمك بالتعامل معه قبل التشغيل: بـ try-catch، أو بالتصريح عنه بـ throws. أمثلة: IOException و FileNotFoundException.

Student.java
package exception; import java.io.FileReader; public class Student {     public static void main(String[] args) {        FileReader file = new FileReader("data.txt");    } }
رسالة NetBeans×
unreported exception FileNotFoundException; must be caught or declared to be thrown
الحل الأول: throws
Student.java
package exception; import java.io.FileNotFoundException;import java.io.FileReader; public class Student {     public static void main(String[] args) throws FileNotFoundException {         FileReader file = new FileReader("data.txt");    } }
الحل الثاني: try-catch
Student.java
package exception; import java.io.FileNotFoundException;import java.io.FileReader; public class Student {     public static void main(String[] args) {        try {            FileReader file = new FileReader("data.txt");        }        catch (FileNotFoundException ex) {            System.out.println(ex.getMessage());        }    } }

في NetBeans يقترح المصباح الحلّين: Add throws clause أو Surround Statement with try-catch.

٢) Unchecked Exception: غير المتحقق (Runtime Exception)

لا يتحقق منه المترجِم، بل يظهر وقت التشغيل، والمبرمج غير مُلزم بمعالجته. ينتج عادةً عن أخطاء منطقية:

Main.java
public class Main {    public static void main(String[] args) {        int[] myNumbers = {1, 2, 3};        System.out.println(myNumbers[10]);   // error!    }}
Output – (run)×
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 3
الجانبCheckedUnchecked
وقت الفحصأثناء الترجمةأثناء التشغيل
إلزام المعالجةإلزامياختياري
أمثلةIOException, SQLExceptionNullPointerException, ArithmeticException

٣) Error

خطأ بسبب حدود الجهاز الذي يعمل عليه البرنامج، ولا يرتبط بالبرنامج مباشرة؛ مثل امتلاء الذاكرة: «JVM is out of memory».

٧معالجة الاستثناءات: try و catch و finally

الكتلةوظيفتها
tryنكتب فيها الكود الذي نتوقع أن يسبب خطأ؛ فإذا وقع الخطأ «ترمي» الاستثناء.
catchتلتقط الاستثناء الذي رمته try وتعالجه، ثم يستمر التنفيذ بعدها.
finallyتُنفَّذ دائمًا سواء حدث استثناء أم لا؛ مثل إغلاق ملف أو اتصال بقاعدة بيانات.
الصيغة العامة
try {    // الكود الذي قد يسبب استثناء}catch (Exception e) {    // التعامل مع الخطأ}finally {    // (اختياري) كود يتم تنفيذه دائمًا}

مثال الدرجات

Main.java
package exception;import java.util.Scanner;public class Main {    public static void main(String[] args) {        double marks [] = {90, 88.50, 77, 100, 50};        Scanner scan = new Scanner(System.in);        int idx;        try {            System.out.print("Enter an index number.(0 to 4): ");            idx = scan.nextInt();            System.out.println("The mark is: " + marks[idx]);        }        catch (Exception e) {            System.out.println(e);        }        finally {            System.out.println("finally block");        }        System.out.println("After try and block");    }}
Output – JavaApplication (run)×
run:
Enter an index number.(0 to 4): The mark is: 50.0
finally block
After try and block
BUILD SUCCESSFUL (total time: 0 seconds)

أدخل 4 فيطبع الدرجة 50.0. جرّب إدخال 5 فيلتقط catch الخطأ ArrayIndexOutOfBoundsException ويكمل البرنامج.

أكثر من catch: لكل خطأ رسالة

Main.java
package exception;import java.util.InputMismatchException;import java.util.Scanner;public class Main {    public static void main(String[] args) {        double marks [] = {90, 88.50, 77, 100, 50};        Scanner scan = new Scanner(System.in);        int idx = 0;        try {            System.out.print("Enter an index number.(0 to 4): ");            idx = scan.nextInt();            System.out.println("The mark is: " + marks[idx]);        }        catch (ArrayIndexOutOfBoundsException e) {            System.out.println("ERROR: the number " + idx +" is out of range");        }        catch(InputMismatchException e){            System.out.println("ERROR: the number you entered is not an integer number");        }        catch(Exception e){            System.out.println("ERROR: " + e);        }        finally {            System.out.println("finally block");        }        System.out.println("After try and block");    }}
Output – JavaApplication (run)×
run:
Enter an index number.(0 to 4): ERROR: the number you entered is not an integer number
finally block
After try and block
BUILD SUCCESSFUL (total time: 0 seconds)

٨منهجية التصحيح: خمس خطوات

التصحيح العشوائي، أي تغيير الكود على أمل أن يعمل، يضيّع الوقت وقد يضيف أخطاء جديدة. المبرمج المنظّم يتبع خطوات ثابتة:

الخطوةماذا تفعل؟
١. أعِد إنتاج الخطأحدّد المدخلات التي تُظهر الخطأ بثبات. خطأ لا تستطيع تكراره لا تستطيع إصلاحه.
٢. حدّد المكاناقرأ رسالة الخطأ، أو استخدم الطباعة ونقاط التوقف لتضييق المكان إلى سطر أو بضعة أسطر.
٣. افهم السببلماذا حدث؟ لا تكتفِ بإخفاء العَرَض. مثلًا: لماذا أصبح المقسوم عليه صفرًا؟
٤. أصلحعدّل أقل قدر ممكن من الكود، وتغييرًا واحدًا في كل مرة.
٥. أعِد الاختبارجرّب المدخلات التي أظهرت الخطأ، ثم بقية الحالات للتأكد أنك لم تُفسد شيئًا آخر.

٩مثال كامل: تصحيح برنامج الفاتورة

المطلوب: اقرأ السعر والكمية، فإذا كان الإجمالي 1000 فأكثر يحصل العميل على خصم 10%. والسعر والكمية يجب أن يكونا أكبر من صفر.

١. نختبر بجدول حالات

المدخلاتالمتوقعالناتج الفعليالحكم
100 و 5الإجمالي: 500.0الإجمالي: 500.0✓
250 و 4الإجمالي: 900.0الإجمالي: 1000.0✗
300 و 4الإجمالي: 1080.0الإجمالي: 1080.0✓
-50 و 2قيمة غير صحيحةالإجمالي: -100.0✗
Main.java
import java.util.Scanner;public class Main {    public static void main(String[] args) {        Scanner in = new Scanner(System.in);        double price = in.nextDouble();        int qty = in.nextInt();        double total = price * qty;        double discount = 0;        if (total > 1000)            discount = total * 0.1;        total = total - discount;        System.out.println("الإجمالي: " + total);    }}

٢. نحدد المكان ونفهم السبب

٣. نصلح ثم نعيد الاختبار

Main.java
import java.util.Scanner;public class Main {    public static void main(String[] args) {        Scanner in = new Scanner(System.in);        double price = in.nextDouble();        int qty = in.nextInt();        if (price <= 0 || qty <= 0) {            System.out.println("قيمة غير صحيحة");            return;        }        double total = price * qty;        double discount = 0;        if (total >= 1000)            discount = total * 0.1;        total = total - discount;        System.out.println("الإجمالي: " + total);    }}
Output – JavaApplication (run)×
run:
250
4
الإجمالي: 900.0
BUILD SUCCESSFUL (total time: 0 seconds)
Main.java
import java.util.Scanner;public class Main {    public static void main(String[] args) {        Scanner in = new Scanner(System.in);        double price = in.nextDouble();        int qty = in.nextInt();        if (price <= 0 || qty <= 0) {            System.out.println("قيمة غير صحيحة");            return;        }        double total = price * qty;        double discount = 0;        if (total >= 1000)            discount = total * 0.1;        total = total - discount;        System.out.println("الإجمالي: " + total);    }}
Output – JavaApplication (run)×
run:
-50
2
قيمة غير صحيحة
BUILD SUCCESSFUL (total time: 0 seconds)

أعدنا تشغيل كل حالات الجدول، فأصبحت كلها ✓. الكلمة return داخل main تنهي البرنامج مبكرًا.

١٠الوقاية: ممارسات البرمجة الجيدة

أفضل خطأ هو الذي لا يحدث. هذه الممارسات تقلل الأخطاء وتجعل اكتشافها أسهل:

الممارسةبدلًا منالأفضل
أسماء واضحةdouble x = a * b;double total = price * qty;
تنسيق ومسافات بادئةكود متلاصق غير مرتبAlt + Shift + F في NetBeans
ثوابت بدل الأرقام السحريةtotal * 0.15final double VAT = 0.15;
دوال صغيرةmain طويلة من 100 سطردالة لكل مهمة، كما في التفكيك
التحقق من المدخلاتالقسمة مباشرةif (count != 0) قبل القسمة
البناء التدريجيكتابة البرنامج كاملًا ثم تشغيلهاكتب جزءًا صغيرًا وشغّله، ثم أضف
تعليقات مفيدةi++; // زيادة iتعليق يشرح السبب لا الواضح

التحقق من المدخلات قبل القراءة

الدالة hasNextInt() تخبرنا هل ما كتبه المستخدم رقم صحيح قبل قراءته، فنتجنب InputMismatchException:

snippet
Scanner in = new Scanner(System.in);System.out.println("ادخل عمرك:");while (!in.hasNextInt()) {    System.out.println("أدخل رقمًا صحيحًا من فضلك:");    in.next();          // تجاهل الإدخال الخاطئ}int age = in.nextInt();System.out.println("عمرك " + age);
Output – JavaApplication (run)×
run:
ادخل عمرك:
عشرون
أدخل رقمًا صحيحًا من فضلك:
20
عمرك 20
BUILD SUCCESSFUL (total time: 0 seconds)

١١قائمة فحص قبل تسليم أي برنامج

توقّع وتحقّق

في الرسالة Main.java:12: error: ';' expected في أي سطر الخطأ؟

الرقم بعد اسم الملف هو رقم السطر.

ما معنى cannot find symbol؟

المترجم لا يعرف هذا الاسم: إملاء خاطئ أو متغير غير معرّف.

في تتبّع المكدّس، أي سطر يحدد مكان الخطأ في برنامجك؟

ابحث عن أول سطر فيه Main.java.

أي استثناء يظهر عند a[5] ومصفوفة طولها 5؟

الخانات من 0 إلى 4، فالخانة 5 خارج الحدود.

ما أول خطوة في منهجية التصحيح؟

لا نستطيع إصلاح خطأ لا نستطيع تكراره.

برنامج أعطى نتيجة صحيحة مع مدخل واحد. هذا يعني:

نختبر عدة حالات، خصوصًا الحدّية.

ما فائدة hasNextInt()؟

تمنع InputMismatchException.

أي اسم متغير أفضل لمجموع الفاتورة؟

الاسم الواضح يقلل الأخطاء.

الخلاصة

تحدٍّ برمجي: أصلح حاسبة المعدل

متقدم

البرنامج يقرأ عدد المواد ثم درجاتها ويطبع المعدل، لكنه يعطي نتيجة خاطئة، ويتوقف إذا كان عدد المواد صفرًا.

المطلوب:
  • استخدم جدول تتبّع أو الطباعة التشخيصية لإيجاد الخطأ المنطقي.
  • أضف تحققًا يمنع القسمة على صفر ويطبع: لا توجد مواد
  • مع المدخلات 3 ثم 80 90 70 يجب أن يطبع: المعدل = 80.0

ابدأ من هذا الكود، واضغط ▶ شغّل هنا لتكتب حلّك وتجرّبه مباشرة:

Main.java
import java.util.Scanner;public class Main {    public static void main(String[] args) {        Scanner in = new Scanner(System.in);        int n = in.nextInt();        int sum = 0;        for (int i = 1; i < n; i++) {            sum = sum + in.nextInt();        }        double avg = sum / n;        System.out.println("المعدل = " + avg);    }}
Output – المطلوب×
المعدل = 80.0
💡 تلميح 1
كم مرة تتكرر الحلقة عندما n = 3؟
💡 تلميح 2
sum / n قسمة صحيحة؛ استخدم (double) sum / n. وضع الشرط if (n == 0) قبل الحلقة.

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

Main.java
import java.util.Scanner;public class Main {    public static void main(String[] args) {        Scanner in = new Scanner(System.in);        int n = in.nextInt();        if (n == 0) {            System.out.println("لا توجد مواد");            return;        }        int sum = 0;        for (int i = 1; i <= n; i++) {            sum = sum + in.nextInt();        }        double avg = (double) sum / n;        System.out.println("المعدل = " + avg);    }}

🧪 تدريبات المذكرة العملية

التدريب العملي 60: Checked و Unchecked في برنامج واحدمن مذكرة العملي: المسألة، والخوارزمية، والمخرجات، والحل.افتح التدريب ←انتقل إلى تقييم الدرسأسئلة الاختيار، وصح وخطأ، وأكمل الفراغات، والأسئلة العملية.ابدأ التقييم ←