مقدمة
كل مبرمج يقضي جزءاً كبيراً من وقته في تصحيح الأخطاء. الفرق بين المبتدئ والمحترف ليس عدد الأخطاء، بل سرعة إيجاد سببها. المبتدئ يغيّر أشياء عشوائياً حتى «يشتغل»؛ والمحترف يتبع منهجاً.
أهداف التعلم
- إعادة إنتاج الخطأ بشكل ثابت.
- قراءة رسالة الخطأ وتتبع الاستدعاءات (Stack Trace).
- عزل المشكلة بتقليص الكود.
- استخدام الطباعة المنظمة والمصحح (Debugger).
- التأكد من الإصلاح بكتابة اختبار.
الخطوة 1: أعد إنتاج الخطأ
قبل أي شيء: ما الخطوات التي تُظهر الخطأ في كل مرة؟ ما المدخلات بالضبط؟ خطأ لا تستطيع إعادة إنتاجه لا تستطيع التأكد من إصلاحه.
الخطوة 2: اقرأ رسالة الخطأ كاملة
Traceback (most recent call last):
File "app.py", line 12, in <module>
print(average(scores))
File "app.py", line 4, in average
return total / len(values)
ZeroDivisionError: division by zero
اقرأ من الأسفل: نوع الخطأ (قسمة على صفر)، ثم أين حدث (السطر 4 داخل average)، ثم من استدعاه (السطر 12). السؤال الآن محدد: لماذا كانت values فارغة؟
الخطوة 3: اعزل المشكلة
قلّص الكود إلى أصغر مثال يُظهر الخطأ. احذف الأجزاء غير المتعلقة واحداً تلو الآخر. كثيراً ما يكتشف المبرمج السبب أثناء كتابة المثال المصغر، وهذا أيضاً أفضل شكل لطرح سؤال في المنتدى.
الخطوة 4: فرضيات وأدوات
ضع فرضية واحدة ثم اختبرها:
def average(values):
print(f"[debug] values={values!r} type={type(values).__name__}")
total = sum(values)
return total / len(values)
!r يُظهر القيمة بشكلها الحقيقي (فيظهر الفرق بين '5' النصي و5 الرقمي). وأفضل من الطباعة: المصحح. في VS Code ضع نقطة توقف (Breakpoint) بالنقر بجانب رقم السطر وشغّل بوضع Debug، ثم افحص المتغيرات خطوة بخطوة. في المتصفح استخدم debugger; أو تبويب Sources في أدوات المطور.
الخطوة 5: أصلح ثم أثبت
اكتب اختباراً يفشل قبل الإصلاح وينجح بعده:
def test_average_empty_list():
assert average([]) is None
def average(values):
if not values:
return None
return sum(values) / len(values)
الاختبار يمنع عودة الخطأ نفسه مستقبلاً.
أشهر الأخطاء التي ستقابلها
- خطأ بفارق واحد (Off-by-one): حلقة تبدأ من 1 بدل 0 أو تتوقف قبل العنصر الأخير بواحد.
- مقارنة نص برقم:
"10" > "9"في كثير من اللغات تعطيFalseلأنها مقارنة نصية. - تعديل القائمة أثناء المرور عليها: يتخطى عناصر أو يكررها؛ امرّ على نسخة أو ابنِ قائمة جديدة.
- قيم فارغة:
Noneأوnullأوundefinedحيث تتوقع كائناً. - غير المتزامن في JavaScript: نسيان
awaitيجعلك تتعامل معPromiseبدل النتيجة. - ذاكرة التخزين المؤقت: تعدل الملف لكن المتصفح أو الخادم يعرض نسخة قديمة.
تمرين عملي
في الكود التالي خطآن. جد كلاً منهما باتباع الخطوات الخمس:
def last_three(items):
return items[len(items) - 3 : len(items) - 1]
print(last_three([1, 2, 3, 4, 5])) # المتوقع [3, 4, 5]
print(last_three([7])) # المتوقع [7]
(تلميح: راجع حدود الشرائح، ثم جرّب items[-3:].)
أخطاء شائعة في عملية التصحيح نفسها
- تغيير عدة أشياء في وقت واحد؛ فلا تعرف أيها أصلح المشكلة.
- إزالة رسائل الخطأ أو التقاطها بصمت (
except: pass) بدل فهمها. - ترك أسطر الطباعة المؤقتة في الكود المنشور.
الخلاصة
أعد الإنتاج، اقرأ الرسالة، اعزل، ضع فرضية واختبرها بالأدوات، ثم أثبت الإصلاح باختبار. ومع الوقت ستتعرف على الأنماط المتكررة بسرعة.
مصادر مقترحة
- مقال «How to Ask a Good Question» في توثيق Stack Overflow.
- توثيق مصحح VS Code الرسمي.
- كتاب Debugging لـ David J. Agans (تسع قواعد بسيطة).