2.41. การดีบัก

สคริปต์ส่วนใหญ่ที่ล้มเหลวบนกล้องจะล้มเหลวในสามแบบ: เกิดข้อยกเว้น, ให้ค่าผิดพลาด, หรือค้างอยู่ แต่ละแบบมีชุดเครื่องมือที่แตกต่างกัน

2.41.1. การอ่าน traceback

เมื่อสคริปต์เกิดข้อยกเว้นและไม่มีอะไรจัดการกับมัน REPL หรือ IDE จะพิมพ์ traceback -- บันทึกของห่วงโซ่การเรียกใช้งานจากสคริปต์ชั้นนอกสุดลงไปถึงบรรทัดที่เกิดข้อยกเว้น

การอ่าน traceback จะอ่านจากล่างขึ้นบน:

  • บรรทัดล่างสุดระบุชื่อคลาสของข้อยกเว้นและข้อความ (ValueError: invalid literal for int()...)

  • แต่ละบล็อก File "...", line N, in <name> ที่อยู่เหนือมันคือ เฟรม -- หนึ่งการเรียกลึกขึ้นเมื่อคุณขึ้นไป

  • เฟรมบนสุดคือจุดที่สคริปต์เริ่มต้น; เฟรมล่างสุดคือจุดที่เกิดข้อผิดพลาด

อ่านส่วนล่างก่อนเพื่อเรียนรู้ว่า อะไร ผิดพลาด จากนั้นเดินขึ้นบนเพื่อดูว่าสคริปต์ ไปถึงจุดนั้นได้อย่างไร หมายเลขบรรทัดชี้ไปที่ตำแหน่งต้นฉบับที่แน่นอนในสคริปต์

2.41.3. การสำรวจอ็อบเจ็กต์

ฟังก์ชันในตัวสองตัวตอบคำถาม "ฉันทำอะไรกับสิ่งนี้ได้บ้าง":

  • dir() -- คืนรายการชื่อทุกอย่างที่กำหนดไว้บนอ็อบเจ็กต์: เมธอด, แอตทริบิวต์, dunders, ทั้งหมด

  • help() -- พิมพ์ docstring (และบน CPython ยังรวมถึง signature) ของฟังก์ชัน เมธอด หรือคลาส

ใช้ทั้งสองร่วมกัน: 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 methods ที่สืบทอดมาจากทุกอ็อบเจ็กต์; ชื่อที่คุ้มค่าในการสแกนมักอยู่หลังจากนั้น 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 -- บางครั้งแค่ signature, บางครั้งเป็น docstring บรรทัดเดียว, บางครั้งไม่มีอะไรสำหรับฟังก์ชัน C ในตัว ถึงอย่างนั้นก็ยังเป็นตัวเตือนที่รวดเร็วเมื่อ tooltip ของ IDE ไม่อยู่ใกล้มือ

2.41.4. เมื่อสคริปต์ค้าง

สคริปต์ที่ไม่คืนค่าวินิจฉัยยากกว่าสคริปต์ที่เกิดข้อยกเว้น สาเหตุที่พบบ่อย:

  • ลูป while ที่เงื่อนไขไม่เคยกลายเป็น false เพิ่ม print ของตัวแปรลูปในแต่ละรอบ; หากค่าไม่เปลี่ยนแปลง ส่วนของลูปมีบั๊ก

  • การเรียกแบบบล็อกที่รอรับอินพุตที่ไม่มาถึง -- การอ่านจากคิวที่ว่างเปล่า, sleep ที่ไม่มีสิ้นสุด ล้อมรอบการเรียกด้วย print เพื่อดูว่าสคริปต์ติดอยู่ที่บรรทัดไหน

  • การเรียกซ้ำแบบไม่สิ้นสุด traceback เมื่อมันยิงในที่สุด (ด้วย RecursionError) มักจะชี้ตรงไปที่จุดนั้น

การกู้คืนที่มีประสิทธิภาพสูงสุดสำหรับสคริปต์ที่ค้างคือปุ่ม stop ของ IDE ซึ่งส่ง KeyboardInterrupt ไปยังสคริปต์ผ่าน USB อินเทอร์รัปต์นี้ปรากฏเป็น traceback ที่บรรทัดที่กำลังทำงานอยู่ -- มักเป็นบรรทัดที่ไม่คืนค่าพอดี

Note

หากการค้างต้านทานการวินิจฉัยทุกอย่าง -- สคริปต์ดูถูกต้อง, traceback ของอินเทอร์รัปต์ชี้เข้าไปในฟังก์ชันในตัวหรือโค้ดเฟิร์มแวร์แทนที่จะเป็นสคริปต์ของคุณ, หรือโค้ดเดิมทำงานได้บนเวอร์ชันเฟิร์มแวร์ก่อนหน้า -- สาเหตุอาจเป็นบั๊กในเฟิร์มแวร์แทนที่จะเป็นบั๊กในสคริปต์ ลดสคริปต์ให้เหลือตัวอย่างที่เล็กที่สุดที่ยังค้างอยู่และเปิด report บน OpenMV forum รวมถึงเวอร์ชันเฟิร์มแวร์, บอร์ดที่ใช้, และสคริปต์ที่ลดลงแล้ว

2.41.5. เอาการวินิจฉัยออกก่อนส่งมอบงาน

การใช้ print อย่างมีกลยุทธ์ระหว่างการพัฒนาเป็นเรื่องดี; การเรียก print หลายร้อยครั้งที่ค้างไว้ในสคริปต์ production ทำให้ผลลัพธ์รกและใช้ heap ที่งานจริงควรจะใช้แทน เมื่อบั๊กได้รับการแก้ไขแล้ว ให้นำ print ออก (หรือซ่อนไว้หลัง debug flag ที่คุณสามารถปิดได้)

สำหรับการวินิจฉัยที่ควรอยู่ในเส้นทางโค้ดระยะยาว ให้เปลี่ยนจาก print() เป็นโมดูล logging มันแนบ ระดับ ไปกับแต่ละข้อความ (debug, info, warning, error) และให้การตั้งค่าเดียวปิดเสียงข้อความระดับล่างใน production:

import logging

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

การตั้งค่าระดับของ logger เป็น logging.WARNING ทำให้การเรียก info และ debug มีต้นทุนแทบไม่มี (ไม่มีการสร้างสตริงข้อความเลย) โดยไม่ต้องคอมเมนต์บรรทัดออก ทำให้ logging เป็นเครื่องมือที่เหมาะสมสำหรับการวินิจฉัย ถาวร; print แบบดิบเหมาะสำหรับการวินิจฉัย แบบชั่วคราว