Покажем, как отладить сложный код просто и без паники. Поговорим о ключевых инструментах и дадим советы, которые помогут даже начинающим разработчикам быстро найти проблему.
012 открытий64 показов
В 2017 году Amazon потерял сотни миллионов долларов из-за одной опечатки.
Сбой длился 4 часа и привёл к сбою в работе Netflix, Airbnb и даже Комиссии по ценным бумагам и биржам США. А виной всему человеческий фактор: сотрудник занимался рутинной отладкой системы, ошибся в команде и отключил сервера, которые отключать было нельзя.
Да, относиться к отладке кода нужно не менее внимательно, чем к его написанию. Поэтому сегодня мы покажем, как отладить сложный код просто и без паники. Поговорим о ключевых инструментах и дадим советы, которые помогут даже начинающим разработчикам быстро найти проблему.
Так отлаживать или дебажить?
Дебажить –– англицизм, который значит то же самое. Используйте тот термин, который вам нравится. А мы будем их чередовать.
Итак, отладка — процесс выявления и исправления ошибок в программном коде.
Ошибки бывают разные:
Синтаксические
Синтаксическая ошибка –– это, например, забытая закрывающаяся скобка или пропущенный отступ.
Здесь синтаксическая ошибка в том, что мы забыли двоеточие в первой строке:
def add(a, b) #ошибка тут, мы забыли двоеточие return a + b
В Python обязательно нужно ставить двоеточие после объявления функции. Без этого язык не поймет, что за двоеточием следует тело функции. Поэтому программа выполнена не будет.
Топ пакетов для улучшения работы с Pythontproger.ru
Как правильно:
def add(a, b): return a + b
Логические
Логические ошибки –– неправильная логика программы. Код может выполняться без проблем в синтаксисе и не выбрасывать исключения, но при этом не давать ожидаемого результата. Такие ошибки трудно отследить, потому что Python не сообщает о них явно.
Допустим, мы пишем программу для проверки чётности:
def is_even(number): return number % 2 == 1 # Здесь логическая ошибка print(is_even(4)) # Ожидаем True, но получаем False
Вместо проверки number % 2 == 0 проверяется number % 2 == 1. Это логическая ошибка.
Исключения
Исключения — ошибки, которые возникают во время выполнения программы. Например, деление на ноль или попытка получить элемент списка по индексу, которого нет.
Разберём деление на ноль. Это действие вызовет исключение ZeroDivisionError:
def divide(a, b): return a / b print(divide(10, 0))
ZeroDivisionError: division by zero
Такие исключения можно обрабатывать, чтобы программа не упала. Используем try-except:
def divide(a, b): try: return a / b except ZeroDivisionError: return "no division by zero in this house!" print(divide(10, 0))
Итак, каждая ошибка требует своего подхода. Но если знать, как правильно дебажить, вы сможете значительно ускорить процесс и сделать свой код качественным, понятным и надёжным.
Перейдём к инструкции по отладке.
Шаг 1: Анализируем ошибки и логи
Перед тем, как отлаживать код, важно понять: что именно пошло не так? Поэтому первый шаг –– анализ.
Типы ошибок мы уже разобрали, поэтому будет полегче. Иногда их не получается увидеть сразу в коде –– если бы всё было так просто! Тогда на помощь приходят логи, которые предоставляет программа или ваша IDE.
Что с ними делать:
- Изучите сообщение об ошибке. Важно понять, где и почему произошёл сбой. Обычно сообщение указывает тип ошибки и место, где она возникла. Мы уже видели такое поведение в предыдущем разделе, когда забыли поставить двоеточие;
- Проверьте логи. В логах будут важные данные об ошибке. Обратите внимание на ключевые слова: Error или Exception;
- Воспроизведите ошибку. Попробуйте выполнить код несколько раз. Возможно, это был случайный баг, который больше не повторится.
Шаг 2: Разделяем задачу на более мелкие части
Сломанный код лучше разбить на более мелкие и понятные части. Это поможет вам найти ошибку быстрее, так как вы точно определите, где её точно нет.
Как это работает:
Если ошибка возникает в сложном фрагменте, то лучше разделить его на более мелкие шаги.
Пример: Если программа загружает файл, обрабатывает данные и записывает результат, проверьте отдельно каждую функцию:
- Загружается ли файл?
- Обработались ли данные?
- Успешно ли записывается результат?
Также можно сузить область поиска ошибки. Если задача большая, начните с проверки небольших фрагментов кода, чтобы изолировать проблемный участок. Загоните ошибку в угол.
Это лучше, чем хаотично бросаться то в одну, то в другую часть кода и искать иголку в стоге сена.
Шаг 3: Воссоздаём проблему
Репродукция ошибки — самая важная задача. Чтобы понять, как исправить ошибку, вам нужно её увидеть и поймать. Иногда она проявляется только при определенных условиях, а при каких –– неясно.
Тут стоит схитрить:
- Попробуйте воспроизвести ошибку при разных входных данных. Может быть, строка не ломает программу, а число –– да;
- Пользуйтесь инструментами отладки и отслеживайте каждый шаг программы.
И тут мы подобрались к самому интересному. Пока что мы говорили о том, как решать проблему. Но не упомянули, с помощью чего. Исправляемся.
Отладчики и логирование
Отладчики встроены в большинство IDE. Например, в PyCharm или Visual Studio есть встроенные отладчики, с помощью которых можно поставить точки останова.
Точка останова (breakpoint) — инструмент, с помощью которого вы можете остановить программу на определенном этапе и изучить её состояние. Например, проверить, какие значения принимают переменные или что именно происходит перед вызовом ошибки.
Как это работает:
- Установите точку останова в нужной строке;
- Запустите программу в режиме отладки;
- Выполнение остановится на этой строке: изучите текущие данные.
Разберём на примере Visual Studio:
Напишем небольшую функцию, в которой рассчитывается площадь прямоугольника:
def main(): width = 5 height = 10 result = width * height print(f"Площадь: {result}") main()
Поставим точки останова на переменных width, height и result:
Перейдём вот в эту панельку:
И запустим дебаггинг:
После запуска в редакторе появится новая панелька –– для управления отладкой:
С помощью неё можно остановить или перезапустить отладку, перейти к следующей точке останова и так далее.
В панели слева появятся значения, за которыми мы следим:
Здесь вы можете увидеть, чему равна каждая переменная.
Напишем функцию с ошибкой, которую уже разбирали:
def main(): colors = ['violet','beige', 'teal'] print(colors[4]) main()
Мы пытаемся получить элемент, индекс которого не существует. Красная плашка предупреждает, что возникло исключение:
List index out of range –– такого индекса в списке нет. Вроде всё понятно, но можно разобраться.
Сбоку мы видим список colors, его элементы и длину len(). В Python, как и во многих других языках, отсчёт начинается с 0. Если в списке 3 элемента, то индекс последнего –– 2. Поэтому мы и получаем ошибку.
Аналитик данных: обзор профессии и курса от MDStproger.ru
Помимо точек останова можно следить за стеком вызовов.
Стек вызовов — цепочка вызванных функций. Она показывает, что происходит на каждом шаге программы.
Это особенно важно при сложных ошибках, скрытых глубоко в коде. Перепишем немного и сделаем несколько программ:
colors = ['violet', 'beige', 'teal'] def get_favorite_color(index): return colors[index] def display_favorite_color(index): color = get_favorite_color(index) print(f"Ваш любимый цвет: {color}") def main(): display_favorite_color(4) main()
Ошибка всё ещё на месте, но теперь её появление происходит не сразу в функции main, а глубже.
И стек вызовов нам показывает, что сначала выполняется main, затем display_favorite_color и уже потом get_favorite_color, где и живёт ошибка:
Эту проблему можно решить с помощью try-except. Советуем попробовать самостоятельно🙂.
И последний способ отладки, который разберём –– логирование. Можно использовать print() или модуль, который уже заботливо написали за нас. Результат одинаковый — вывод нужных данных в определённое время. Так мы поймём, что лежит в переменной и почему программа ломается.
Примеры с print мы уже разбирали в предыдущих шагах. Метод простой и лёгкий –– то, что нужно для новичков.
Разберём вариант посложнее с модулем logging. Его нужно импортировать и настроить:
import logging #импорт logging.basicConfig(level=logging.DEBUG, #строка 3 format='%(asctime)s - %(levelname)s - %(message)s', ) #настройка def get_favorite_color(index): logging.info("Получение цвета") return colors[index] def display_favorite_color(index): color = get_favorite_color(index) logging.debug(f"Ваш любимый цвет: {color}") def main(): display_favorite_color(2) main()
Теперь подробнее про конфигурацию. В logging есть несколько типов сообщений:
- Отладочные –– DEBUG. Они показывают подробности о значениях переменных или работе отдельных функций;
- Информационные –– INFO. В них можно прописывать статусы выполнения программы;
- Предупреждения –– WARNING. Сообщения с потенциальными проблемами;
- Ошибки –– ERROR и CRITICAL.
Мы указали, что хотим видеть все сообщения с помощью строки 3. Теперь в консоли будет отображаться все, что нужно: предупреждения, ошибки и обычные сообщения отладки.
А на следующей строке указываем, в каком формате хотим видеть сообщение. В итоге получаем точное время, тип сообщения и само сообщение:
Это намного удобнее, чем прописывать все данные в print вручную.
Если нам нужны только ворнинги, то меняем конфигурацию:
logging.basicConfig(level=logging.WARNING)
Шаг 4: Тестируем — юнит-тесты и их роль
Когда речь идёт об отладке кода, нельзя забывать о тестах. Потому что они помогают выявить ошибки и опасные места на моменте разработки, когда их ещё легко заметить и поправить.
Юнит-тесты — отличный способ проверять, что каждый блок вашего кода работает как положено.
Они особенно полезны в отладке сложных программ и приложений, где ошибки могут проявляться только после нескольких шагов или при конкретных входных значениях. Например, при списках.
Для этого можно использовать pytest или unittest.
Давайте поработаем с pytest. Напишем в файле main.py очень простую функцию:
def main(num): return num ** 2
Функция принимает число и возвращает квадрат числа.
Напишем тест. Для этого нужно установить pytest в свою IDE и создать файлик. Например, test.py:
from main import main def test_main(): assert main(2) == 4
Наша гипотеза: 2 в квадрате равно 4. Для того, чтобы её проверить, используем assert.
Если в терминале запустить команду pytest test.py, то увидим, что всё прошло успешно:
Если же заменить 4 на 5, то тест упадёт. Так как 2 в квадрате –– не 5:
Советы по улучшению процесса отладки
Мы разобрались, как дебажить и тестировать код. Напоследок дадим три базовых совета, которые актуальны не только для Python, но и для любого языка программирования.
Делите код на небольшие модули
Разбивайте программу на маленькие независимые части. Так их легче тестировать и искать ошибки. Например, вместо одной огромной функции лучше написать несколько компактных. Каждая из них будет отвечать за конкретную задачу. Рефакторинг поможет упростить логику и предотвратить дублирование кода.
Пишите автоматические тесты
Автоматическое тестирование позволяет ловить ошибки на ранних этапах. Вы можете и не увидеть их сразу. Зато они вас видят.
Поэтому после написания каждой новой функции добавляйте тесты. Это избавит от сюрпризов в будущем, когда изменение одной программы неожиданно ломает другую.
Комментируйте код
Не забывайте комментировать код. Объяснять, зачем и как работает каждая функция. Комментарии помогут и вам, и вашей команде быстрее разобраться в коде, особенно спустя несколько месяцев.
Ну и конечно, называйте функции и переменные понятно. Не усложняйте жизнь себе и коллегам. То, что изначально сделано качественно и с умом, с меньшей вероятностью придётся дебажить.
Но если всё-таки пришлось, следуйте нашей инструкции, и всё обязательно получится.