استراتيجيات تصحيح الأخطاء
درس 8 من 8
8استراتيجيات تصحيح الأخطاء (Debugging Strategies)
الصورة الكبيرةمن قراءة الـ Traceback بثقة، إلى print الذكي، إلى pdb التفاعلي، إلى logging الاحترافي، ترتيب الأدوات من الأبسط للأعمق.
ماذا تشاهد في الرسم: الضوء يبدأ من آخر سطر حيث نوع الخطأ، ثم يصعد إلى السطر الذي وقع فيه، ثم إلى من ناداه.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
تصحيح الأخطاء عمل محقّق: رسالة الخطأ هي شهادة الشاهد، تقول لك أين وقعت الجريمة. ثم تضع كاميرات صغيرة (طباعة) لترى ما حدث، أو توقف الزمن وتفتّش المكان خطوة خطوة (المصحّح). والمحقّق المحترف يترك سجلًّا مكتوبًا يقرؤه من يأتي بعده.
اقرأ Traceback من الأسفل: آخر سطر نوع الخطأ ورسالته، وما فوقه سلسلة الاستدعاءات. print(f"{x=}") سريع للفحص، وbreakpoint() يفتح pdb لتتحرك بـn وs وp. وفي الكود الحقيقي استخدم logging بمستويات DEBUG وINFO وERROR بدل الطباعة.
الأساسيات المطلوبة
| المفهوم | لماذا يهمّك هنا |
|---|---|
| معالجة الاستثناءات (درس الاستثناءات) | الـ Traceback هو تفصيل الاستثناء الذي لم يُعالَج، فهم try/except يسهّل فهمه. |
| الدوال (درس الدوال) | تتبّع البيانات عبر عدة دوال متداخلة هو جوهر مهارة التصحيح. |
المفاهيم الأساسية
التصحيح ليس "تخمين ما الخطأ" بل عملية منظّمة: اقرأ الدليل المتاح، كوّن فرضية، اختبرها بأقل تدخّل ممكن، وكرر حتى تصل للسبب الجذري، لا فقط للعرض الظاهري للمشكلة.
1قراءة الـ Traceback
اقرأه من الأسفل للأعلى: آخر سطر يخبرك بنوع الاستثناء ورسالته، والسطر الذي يسبقه يخبرك بالضبط أي سطر كود تسبّب به.
2Print Debugging
أسرع أداة وأبسطها: اطبع قيمة متغيّر في لحظة مشبوهة لترى هل هي ما تتوقعه. سريع لكن يحتاج إزالة يدوية بعد الانتهاء.
3pdb التفاعلي
بدل الطباعة المتكررة، breakpoint() يوقف البرنامج في مكانه بالضبط ويعطيك سطر أوامر تفاعلي للتفتيش.
4Logging بدل Print
سجلّات دائمة بمستويات خطورة (DEBUG/INFO/WARNING/ERROR) يمكن تفعيلها أو تعطيلها دون حذف أي سطر كود.
الرسم 19: التصحيح حلقة منظّمة لا تخمين عشوائي: كل فرضية فاشلة تضيف معلومة جديدة تقرّبك من السبب الحقيقي.
مثال تطبيقي: دالة حساب متوسط تنهار بصمت
لنتتبّع نفس الخطأ بأربع أدوات مختلفة، من الأبسط للأعمق:
## الكود المعطوب — لا تستطيع رؤية الخطأ بالعين مباشرة
def calculate_average(scores):
total = sum(scores)
return total / len(scores)
def process_batch(all_scores):
results = []
for student_scores in all_scores:
results.append(calculate_average(student_scores))
return results
batches = [[90, 85, 88], [70, 75], []] # الطالب الثالث بلا درجات!
print(process_batch(batches))
1️⃣ قراءة Traceback
Traceback (most recent call last):
File "report.py", line 11, in <module>
print(process_batch(batches))
File "report.py", line 8, in process_batch
results.append(calculate_average(student_scores))
File "report.py", line 3, in calculate_average
return total / len(scores)
ZeroDivisionError: division by zero
اقرأه من الأسفل: ZeroDivisionError في السطر 3 داخل calculate_average. السبب الجذري الحقيقي أعلى في السلسلة: قائمة [] فارغة وصلت أساسًا من batches.
الرسم 20: نفس استدعاء calculate_average يتكرر ثلاث مرات بنفس المنطق؛ الانهيار يظهر فقط في الاستدعاء الثالث، لكن مصدر المشكلة الحقيقي أعلى في السلسلة، داخل batches نفسها.
2️⃣ Print Debugging
def calculate_average(scores):
print("DEBUG scores =", scores) # تحقق سريع من القيمة الفعلية
total = sum(scores)
return total / len(scores)
3️⃣ pdb التفاعلي
def calculate_average(scores):
breakpoint() # البرنامج يتوقف هنا تمامًا، يعطيك سطر أوامر (pdb)
total = sum(scores)
return total / len(scores)
## داخل pdb: اكتب p scores لرؤية القيمة، n للسطر التالي، c للمتابعة
داخل جلسة pdb التفاعلية، أهم الأوامر: p <var> لطباعة قيمة متغيّر في لحظتها الحقيقية، n (next) لتنفيذ السطر التالي دون الدخول في الدوال المستدعاة، s (step) للدخول داخل الدالة المستدعاة خطوة بخطوة، و c (continue) لاستكمال التنفيذ العادي حتى نقطة توقف تالية أو نهاية البرنامج. الفرق الجوهري عن print: تستطيع أن تفحص أي متغيّر تخطر لك فكرته لحظيًا دون تعديل الكود أو إعادة التشغيل من جديد.
4️⃣ Logging احترافي بدل الطباعة
import logging
logging.basicConfig(level=logging.DEBUG, format="%(levelname)s: %(message)s")
def calculate_average(scores):
logging.debug(f"scores={scores}")
if not scores:
logging.warning("قائمة درجات فارغة — سيتم تجاهل هذا الطالب")
return 0.0
return sum(scores) / len(scores)
مثال محلول
average([]) تنهار بـZeroDivisionError. الجواب: اقرأ آخر سطر في التتبّع أولًا، فهو يقول القسمة على صفر، ثم السطر فوقه يدلّك على total / len(items). الحل شرط قبله: if not items: return 0.خرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
| أول سطر في Traceback هو مكان المشكلة الحقيقي دائمًا | غالبًا يُظهر فقط أين "ظهرت" المشكلة أخيرًا؛ السبب الجذري قد يكون عدة استدعاءات أعلى في السلسلة. |
| Print Debugging أداة "مبتدئين" يجب تجاوزها بسرعة | مبرمجون محترفون يستخدمونها يوميًا للتحقق السريع، المشكلة فقط في نسيان حذفها أو الاعتماد عليها حصريًا لمشاكل معقّدة. |
| logging مجرد print "مزخرف" بلا فائدة إضافية | logging يضيف مستويات خطورة قابلة للتصفية، توقيت تلقائي، وإمكانية التوجيه لملف أو نظام مراقبة خارجي دون تعديل كود الاستدعاء نفسه. |
أين تُستخدم هذه الاستراتيجيات فعليًا؟
أنظمة الإنتاج (Production)
logging بمستويات مختلفة يتيح تفعيل تفاصيل أكثر فقط وقت التحقيق في مشكلة، دون إيقاف الخدمة.
مراجعة الكود (Code Review)
القدرة على قراءة Traceback بسرعة توفّر وقتًا هائلًا عند تتبّع تقرير خطأ من زميل.
تصحيح كود الآخرين (Legacy Code)
pdb أداة أساسية لفهم تدفّق كود معقّد لم تكتبه أنت، خطوة بخطوة، دون تعديله.
المراقبة (Monitoring)
سجلّات logging المنظّمة تُغذّي عادة أنظمة تنبيه تلقائية عند حدوث أخطاء متكررة في الإنتاج.
جرّب بنفسك
أين تبدأ قراءة رسالة Traceback؟
آخر سطر يقول نوع الخطأ وسببه، والسطر فوقه يقول أين وقع. ثم تصعد لترى من نادى ماذا.
بطاقات مراجعة سريعة
اختبر فهمك
1. في مثال calculate_average أعلاه، لماذا السطر الذي يحمل ZeroDivisionError ليس السبب الجذري الحقيقي؟
لأن الدالة نفسها سليمة منطقيًا، المشكلة الحقيقية أن قائمة فارغة وصلت إليها أساسًا من batches في الأعلى. إصلاح السطر فقط (مثل تجاهل القسمة) يعالج العرض، لا يمنع تمرير بيانات فاسدة من مصدرها.
2. ما الفرق العملي بين logging.debug() و logging.warning()؟
كلاهما يسجّل رسالة، لكن بمستوى خطورة مختلف. ضبط level=logging.WARNING في الإعدادات يُخفي رسائل debug تلقائيًا دون حذف أي سطر كود، مفيد لتصفية التفاصيل الكثيرة في الإنتاج مع الاحتفاظ بالتحذيرات المهمة فقط.
3. لماذا يُفضَّل pdb على كثرة أسطر print عند تتبّع خطأ معقّد يمر بعدة دوال؟
لأن pdb يسمح لك بالتفتيش التفاعلي الحي في كل نقطة توقف (قيم كل المتغيّرات المتاحة، تنفيذ خطوة بخطوة) دون الحاجة لتعديل الكود وإضافة/حذف أسطر طباعة في كل مرة تريد فحص شيء جديد.
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
توثيق رسمي2
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.