2.41. การดีบัก¶
สคริปต์ส่วนใหญ่ที่ล้มเหลวบนกล้องจะล้มเหลวในสามแบบ: เกิดข้อยกเว้น, ให้ค่าผิดพลาด, หรือค้างอยู่ แต่ละแบบมีชุดเครื่องมือที่แตกต่างกัน
2.41.1. การอ่าน traceback¶
เมื่อสคริปต์เกิดข้อยกเว้นและไม่มีอะไรจัดการกับมัน REPL หรือ IDE จะพิมพ์ traceback -- บันทึกของห่วงโซ่การเรียกใช้งานจากสคริปต์ชั้นนอกสุดลงไปถึงบรรทัดที่เกิดข้อยกเว้น
การอ่าน traceback จะอ่านจากล่างขึ้นบน:
บรรทัดล่างสุดระบุชื่อคลาสของข้อยกเว้นและข้อความ (
ValueError: invalid literal for int()...)แต่ละบล็อก
File "...", line N, in <name>ที่อยู่เหนือมันคือ เฟรม -- หนึ่งการเรียกลึกขึ้นเมื่อคุณขึ้นไปเฟรมบนสุดคือจุดที่สคริปต์เริ่มต้น; เฟรมล่างสุดคือจุดที่เกิดข้อผิดพลาด
อ่านส่วนล่างก่อนเพื่อเรียนรู้ว่า อะไร ผิดพลาด จากนั้นเดินขึ้นบนเพื่อดูว่าสคริปต์ ไปถึงจุดนั้นได้อย่างไร หมายเลขบรรทัดชี้ไปที่ตำแหน่งต้นฉบับที่แน่นอนในสคริปต์
2.41.2. การดีบักด้วย print¶
วิธีที่เร็วที่สุดในการค้นหาว่าสคริปต์กำลังทำอะไรอยู่คือการพิมพ์ค่าที่น่าสงสัย ฟังก์ชันในตัวสามตัวทำให้ print มีประโยชน์มากขึ้น:
repr()-- คืนสตริงแบบนักพัฒนาสำหรับค่าprint(repr(value))แยกแยะ"5"ออกจาก5และNoneออกจาก"None"ซึ่งprint()แบบธรรมดาไม่สามารถทำได้type()-- คืนคลาสของค่าprint(type(value))ใช้เพื่อค้นหาว่าตัวแปรที่ "ควรจะเป็น int" จริงๆ แล้วเป็นสตริงลับหรือเปล่าlen()-- ความยาวของลำดับหรือคอลเล็กชัน บั๊กจำนวนมากที่น่าแปลกใจมักเป็นปัญหา off-by-one หรือขนาดไม่ตรงกัน
print("got:", repr(value), "type:", type(value), "len:", len(value))
วางคำสั่ง print ไว้ในแต่ละสาขาที่คุณสนใจ -- ทั้งสองแขนของ if แต่ละบล็อก except ส่วนของลูปที่คุณสงสัยว่าทำงานเป็นศูนย์ครั้ง ต้นทุนคือหนึ่งบรรทัดของผลลัพธ์; คุณค่าคือการค้นหาว่าเส้นทางโค้ดที่คุณ คิดว่า กำลังทำงานอยู่นั้นเป็นเส้นทางที่ทำงานจริงหรือไม่
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 แบบดิบเหมาะสำหรับการวินิจฉัย แบบชั่วคราว