2.41. تصحيح الأخطاء

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

2.41.1. قراءة سجل التتبع العكسي

عندما يطلق برنامج نصي استثناءً ولا تتولى أي جهة معالجته، يطبع REPL أو الـ IDE سجل تتبع عكسي -- وهو سجل بسلسلة الاستدعاءات بدءًا من البرنامج النصي الخارجي وصولًا إلى السطر الذي أطلق الاستثناء.

يُقرأ سجل التتبع العكسي من الأسفل إلى الأعلى:

  • السطر الأخير يحمل اسم فئة الاستثناء ورسالته (ValueError: invalid literal for int()...).

  • كل كتلة من نوع File "...", line N, in <name> فوقه هي إطار -- استدعاء أعمق بمقدار واحد كلما صعدت للأعلى.

  • الإطار العلوي تمامًا هو حيث بدأ البرنامج النصي؛ والإطار السفلي تمامًا هو حيث أطلق الخطأ.

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

2.41.3. استكشاف كائن

دالتان مدمجتان تجيبان على سؤال "ماذا يمكنني أن أفعل بهذا الشيء":

  • dir() -- تُرجع قائمة بكل اسم معرّف على كائن: الدوال (الطرق)، والسمات، وأساليب الـ dunder، وكل شيء.

  • help() -- تطبع سلسلة التوثيق (وعلى CPython، التوقيع) لدالة أو طريقة أو فئة.

استخدمهما معًا: dir يجد الاسم، و help يشرح ما يفعله.

2.41.3.1. إيجاد اسم باستخدام dir

>>> dir([1, 2, 3])
['__add__', '__class__', '__contains__', '__delitem__',
 '__eq__', '__ge__', ..., 'append', 'clear', 'copy',
 'count', 'extend', 'index', 'insert', 'pop', 'remove',
 'reverse', 'sort']

الجزء الأول من القائمة هو طرق الـ dunder الموروثة من كل كائن؛ والأسماء الجديرة بالبحث عنها عادةً ما تأتي بعدها. يعمل dir على أي شيء -- فئة، أو نسخة، أو وحدة، أو نوع مدمج:

>>> import json
>>> dir(json)
['__name__', 'dump', 'dumps', 'load', 'loads']

تلك الصيغة الثانية هي الأسلوب لمعرفة الأسماء عالية المستوى التي تعرضها وحدة ما فعلًا دون مغادرة REPL.

2.41.3.2. البحث عنه باستخدام help

بمجرد أن يُظهر dir مرشحًا، يصفه help:

>>> help(str.split)
split(sep=None, maxsplit=-1)
    Return a list of the words in the string, ...

على MicroPython، يكون help أبسط مما هو على CPython -- أحيانًا مجرد التوقيع، وأحيانًا سلسلة توثيق من سطر واحد، وأحيانًا لا شيء بالنسبة للدوال المدمجة المكتوبة بلغة C. لكنه يبقى تذكيرًا سريعًا عندما لا تكون تلميحة الـ IDE في متناول اليد.

2.41.4. عندما يتوقف شيء ما معلّقًا

البرنامج النصي الذي لا يعود أصعب في التشخيص من البرنامج الذي يطلق استثناءً. الأسباب الشائعة:

  • حلقة while لا يصبح شرطها خاطئًا أبدًا. أضف طباعة لمتغير الحلقة في كل تكرار؛ إذا لم تكن القيمة تتغير، فإن في جسم الحلقة خطأً.

  • استدعاء حاجب ينتظر مدخلًا لا يصل أبدًا -- قراءة من طابور فارغ، أو سكون بلا نهاية. أحط الاستدعاء بأوامر طباعة لترى عند أي سطر علق البرنامج النصي.

  • تعاود لا نهائي. سجل التتبع العكسي عندما يُطلق في النهاية (مع RecursionError) عادةً ما يشير إليه مباشرةً.

أكثر طرق الاستعادة فعالية لبرنامج نصي معلّق هي زر stop في الـ IDE، الذي يرسل KeyboardInterrupt إلى البرنامج النصي عبر USB. تظهر المقاطعة كسجل تتبع عكسي عند السطر الذي يعمل حاليًا -- وغالبًا ما يكون هو السطر الدقيق الذي لا يعود.

ملاحظة

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

2.41.5. أزل أدوات التشخيص قبل التسليم

أوامر الطباعة الاستراتيجية أثناء التطوير رائعة؛ لكن ترك مئة استدعاء طباعة في برنامج نصي إنتاجي يفوضي المخرجات ويستهلك من الكومة (heap) ما كان يمكن أن يستخدمه العمل الحقيقي بدلًا منها. عند إصلاح خطأ، أزل أوامر الطباعة (أو احرسها خلف راية تصحيح يمكنك إيقافها).

للتشخيصات التي ينبغي أن تبقى في مسار الشيفرة على المدى الطويل، انتقل من print() إلى الوحدة logging. فهي ترفق مستوى بكل رسالة (debug و info و warning و error) وتتيح لإعداد واحد إسكات الرسائل الهادئة في الإنتاج:

import logging

log = logging.getLogger("main")
log.info("starting up")
log.debug("loaded config: %s", config)
log.warning("falling back to defaults")

ضبط مستوى المسجّل إلى logging.WARNING يجعل استدعاءات info و debug لا تكلف شيئًا تقريبًا (لا تُبنى سلسلة الرسالة أبدًا)، دون الحاجة إلى تعطيل الأسطر بالتعليق. وهذا يجعل logging الأداة المناسبة للتشخيصات الدائمة؛ بينما print الخام مناسب للتشخيصات العابرة.