Ошибки и отладка

Чтение traceback и типичные ошибки

Содержание курса

Процедура локализации ошибки по traceback

Зная анатомию traceback, можно выработать устойчивый алгоритм: что читать первым, что — вторым, и где искать ответ на вопрос «что сломалось и где».

Алгоритм выглядит так:

  1. Найти строку Traceback (most recent call last): — она отмечает начало сообщения.

  2. Перейти сразу в конец: прочитать последнюю строку — там тип исключения и его описание. Именно это говорит, что пошло не так.

  3. Подняться на одну строку выше — это последний кадр стека. Он показывает где именно: файл и номер строки.

  4. Открыть указанный файл, перейти к этой строке.

  5. Сопоставить тип ошибки с тем, что написано в коде, и исправить.

Шаги 2 и 3 — самые важные. Всё остальное в стеке — контекст, который нужен только если первопричина не очевидна.

Типичная ловушка: читать traceback сверху вниз

Начинающие видят многострочный вывод и начинают читать с первой строки — и немедленно вязнут в цепочке вызовов. В итоге они смотрят не на то место, где ошибка произошла, а на то, с чего программа началась. Это бесполезно.

Правильное направление — снизу вверх. Последняя строка — это диагноз. Предпоследний кадр — это адрес. Только после того, как прочитаны оба, имеет смысл подниматься выше по стеку, если нужно понять цепочку вызовов.

Вот пример: Python вывел такой traceback:

Traceback (most recent call last):
  File "app.py", line 10, in <module>
    show_result()
  File "app.py", line 6, in show_result
    print(reslt)
NameError: name 'reslt' is not defined

Читаем снизу:

  • NameError: name 'reslt' is not defined — имя не определено.
  • Последний кадр: File "app.py", line 6, in show_result — идём в app.py, строка 6.
  • Видим print(reslt) — опечатка в имени переменной.

В стек выше (line 10, in <module>) заходить не нужно: там просто вызов функции, сама ошибка — внутри неё.

Когда нужно подниматься выше по стеку? Только если в последнем кадре код выглядит корректно сам по себе, и причина ошибки кроется в том, с какими данными его вызвали — то есть проблема возникла раньше, на уровне вызывающего кода. Для большинства ошибок начального уровня достаточно двух нижних строк.