2.41. Налагодження

Більшість скриптів, що не працюють на камері, зазнають невдачі одним із трьох способів: вони генерують виняток, повертають неправильне значення або зависають. Для кожного випадку є окремий набір інструментів.

2.41.1. Читання трасування

Коли скрипт генерує виняток і нічого його не перехоплює, REPL або IDE виводить трасування – запис ланцюга викликів від зовнішнього скрипту до рядка, де виник виняток.

Трасування читається знизу вгору:

  • Нижній рядок називає клас винятку та його повідомлення (ValueError: invalid literal for int()...).

  • Кожен блок File "...", line N, in <name> вище – це кадр – один виклик глибше, якщо рухатися вгору.

  • Найвищий кадр – це місце, де скрипт почався; найнижчий кадр – де виникла помилка.

Спочатку прочитайте нижній рядок, щоб дізнатися що пішло не так, а потім рухайтеся вгору, щоб побачити як скрипт до цього дійшов. Номери рядків вказують на точні місця у вихідному коді скрипту.

2.41.3. Дослідження об’єкта

Дві вбудовані функції відповідають на питання «що я можу зробити з цим?»:

  • dir() – повертає список усіх імен, визначених для об’єкта: методи, атрибути, спеціальні методи тощо.

  • 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']

Перша частина списку – це спеціальні методи, успадковані від кожного об’єкта; корисні для перегляду імена зазвичай знаходяться після них. 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, умова якого ніколи не стає хибною. Додайте виведення змінної циклу на кожній ітерації; якщо значення не змінюється, є помилка в тілі циклу.

  • Блокуючий виклик, що очікує введення, яке ніколи не надходить – читання з порожньої черги, нескінченне очікування. Огорніть виклик операціями print, щоб побачити, на якому рядку скрипт застряг.

  • Нескінченна рекурсія. Трасування, яке врешті-решт виникне (з RecursionError), зазвичай відразу вказує на неї.

Найефективніший спосіб відновлення після зависання скрипту – кнопка stop в IDE, яка надсилає KeyboardInterrupt до скрипту через USB. Переривання з’являється як трасування на рядку, що виконується в цей момент – часто саме на тому рядку, який не повертає керування.

Примітка

Якщо зависання не піддається жодній діагностиці – скрипт виглядає правильно, трасування переривання вказує на вбудований код або код мікропрограми замість вашого скрипту, або той самий код працював у попередній версії мікропрограми – причиною може бути помилка мікропрограми, а не скрипту. Зведіть скрипт до найменшого відтворюваного прикладу, що все одно зависає, і опублікуйте звіт на форумі OpenMV. Вкажіть версію мікропрограми, плату та зведений скрипт.

2.41.5. Приберіть діагностику перед розгортанням

Стратегічне використання print під час розробки чудово виправдовує себе; але сотня викликів print, залишена у виробничому скрипті, засмічує виведення і витрачає купу пам’яті, яку могла б використовувати основна робота. Коли помилку виправлено, приберіть print-виклики (або захистіть їх за прапором налагодження, який можна вимкнути).

Для діагностики, яка має залишатися в коді надовго, перейдіть від 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 добре підходить для тимчасової.