Отладка: логи, трассировка, типичные ошибки новичков🔗
Баг как кулинарный провал🔗
Баг — это несоответствие между ожидаемым блюдом и тем, что реально получилось на выходе. Если рецепт предписывает V=2 литра супа, а на выходе V=0.5 — перед вами баг, требующий расследования.
Чтобы его устранить, работают в три приёма:
Диаграмма загружается…
| Кухня |
Программирование |
| Суп слишком солёный |
Переменная total содержит неверное значение |
| Пирог не поднялся |
Результат вычисления равен 0 |
| Ингредиент сгорел |
Значение перезаписано до использования |
Стандарты оформления кода мы оставим для лекции 12 — здесь фокус на локализации ошибок. Воспроизведение фиксирует симптомы, изоляция находит «испорченный ингредиент», исправление — замена значения.
Пример:
salt = 50
water = 1000
soup = salt * water
print(soup) # Ожидали 1050, получили 50000?
Вместо догадок по внешнему виду блюда используют логирование — как дегустацию на каждом этапе — и трассировку, которая ведёт архив заказов.
Логирование: дегустация на каждом этапе🔗
Повар не ждёт финального банкета, чтобы оценить блюдо: он дегустирует соус после обжарки, проверяет плотность теста перед духовкой, фиксирует в блокноте «15:30 — слишком кисло». Логирование — это такие же промежуточные пробники состояния программы.
Уровни важности записей — как степени критичности замечаний су-шефу:
| Уровень |
Значение |
Кулинарный аналог |
DEBUG |
Технические детали |
Запись температуры плиты каждую минуту |
INFO |
Штатный процесс |
«Бульон поставлен, время варки: 2ч» |
ERROR |
Сбой операции |
«Молоко свернулось, требуется замена» |
На практике мы расставляем метки в коде — вывод значений ключевых переменных перед вычислениями:
temperature = 85
cooking_time = 45
print("[DEBUG] Начало: t=" + str(temperature) + ", time=" + str(cooking_time))
temperature = temperature + 15
print("[INFO] После нагрева: t=" + str(temperature))
error_code = 0
print("[ERROR] Код ошибки: " + str(error_code))
В реальных проектах записи направляют в файлы или системы мониторинга, но суть остаётся прежней: фиксация состояния на ключевых шагах рецепта позволяет отследить, в какой момент блюдо «испортилось».
Трассировка и стек вызовов: архив заказов🔗
Когда заказ поступает на кухню, су-шеф кладёт листок с задачей поверх стопки. Если повар вызывает помощника для нарезки, в стек добавляется ещё одна запись — теперь помощник работает, а повар ждёт. Это стек вызовов: последний пришёл — первым выполнился (LIFO). Каждый уровень — фрейм с локальными ингредиентами и адресом возврата. Когда функция завершается через return, фрейм удаляется, и управление возвращается к предыдущему уровню.
Диаграмма загружается…
При ошибке Python выводит traceback — снимок стека снизу вверх в формате File -> line -> in function. Верхняя строка — точка аварии, нижние — цепочка вызовов, показывающая, кто инициировал проблему. Читать traceback стоит снизу: это путь от входной точки программы до места падения.
В отладчике есть два режима трассировки: Step Over — выполнить вызов как чёрный ящик, не заглядывая внутрь (довериться помощнику), и Step Into — войти внутрь функции и проверить каждое действие пошагово.
# Пример traceback при делении на ноль
Traceback (most recent call last):
File "kitchen.py", line 3, in <module>
cook()
File "kitchen.py", line 7, in cook
chop()
File "kitchen.py", line 11, in chop
result = 1 / 0
ZeroDivisionError: division by zero
Типичные ошибки новичков: паттерны-призраки🔗
Даже при точном соблюдении пропорций на кухне возникают «призраки» — логические ошибки, которые не сопровождаются дымом аварий, но портят итоговый результат. Они бесшумно искажают данные, возвращают пустоту или смешивают типы.
| Ошибка |
Симптом в кулинарном процессе |
Метод отладки |
Забытая соль (отсутствует return) |
Блюдо подано, но тарелка содержит None |
Логирование результата: print("out:", volume) |
| Сдвиг в органайзере (off-by-one) |
Обратились к 5-й ячейке вместо 4-й, пересыпав лишнее |
Печать индекса и границ перед выборкой |
| Испорченная кладовая (мутация аргумента) |
Изменили ингредиент в общем хранилище вместо локальной копии |
Лог состояния до операции и после |
| Несовместимость ингредиентов (неявное приведение) |
Сложили строку "2" с числом 3, получив "23" |
Проверка типов операндов в логах |
Рассмотрим поимку пустого значения на практике:
soup_volume = cook_soup()
print("DEBUG: получено", soup_volume)
Если в консоли появляется DEBUG: получено None, вы поймали призрака — функция завершилась без возврата данных. Далее разберём системную стратегию борьбы с такими сбоями.
Научный метод: от шефа-следопыта🔗
Отладка — не гадание на кофейной гуще, а эксперимент. Представьте шеф-повара: он выдвигает гипотезу («суп пересолен из-за salt=2∗x»), проводит пробу и только потом корректирует рецепт:
salt = 2 * x
print("probe:", salt)
Логи используйте для проверки ингредиентов (данных), а трассировку — когда теряетесь в последовательности этапов (потоке). Золотое правило: не исправляйте, пока не объяснили причину, иначе «магическое шаманство» вернёт баг завтра.
Чистый код отлаживать проще (лекция 12), но сейчас мы учимся находить ошибки даже в чужих спагетти-кодах. Как писать, чтобы багов изначально было меньше, — в следующей лекции.