Баги, ворнинги и исключения: что это такое
Оглавление:
Откуда взялось слово bug
Существует множество историй о происхождении термина bug и о том, как он стал применяться в программировании. Самая реалистичная и популярная версия следующая: американский ученый Грейс Мюррей Хоппер обнаружила, что компьютер Harvard Mark II выдает неправильные ответы. Когда она более внимательно осмотрела машину, пытаясь найти проблему – была найдена раздавленная моль, которая попала между контактами электромеханического реле. Насекомое не давало реле полностью замкнуться; таким образом и появился первый компьютерный bug. Хоппер извлекла мотылька пинцетом и записала его в операторский журнал с комментарием: «Найден первый реальный bug». Хоппер же впервые письменно использовала термин «дебаггинг» по отношению к программированию машин.
Так выглядел первый компьютерный bug
Другие версии о происхождении жаргонизма bug – не так реалистичны. Возможно, истинную этимологию этого слова мы так и не узнаем никогда.
Разновидности ошибок
Попробуем классифицировать все сбои в коде на четыре большие категории: лексические, синтаксические, ошибки времени выполнения и логические.
Лексические ошибки
Возникает всякий раз, когда программа содержит слово или символ, которых нет в словаре конкретного языка программирования (Python, например) или которые были ранее связаны с определенным значением.
Пример ошибки лексемы в C++
#include <iostream>
using namespace std;
int main() {
printf("Я пошел гулять");$
return 0;
}
Появление недопустимых символов
В качестве аналогии предположим, что мы находимся на улице Ленина в городе Реутов и заблудившийся автомобилист спрашивает у нас: «Как мне добраться до мавзолея в Москве?». Если мы ответим: «Просто поверните напоаво, затем вафвдите из города, после дойёдете 666меровl и попадете на трассу М*00$» – мы совершим несколько лексических ошибок. Автомобилист не сможет следовать нашим инструкциям, потому что он просто не сможет расшифровать некоторые слова, из которых составлены инструкции. Точно так же интерпретатор Python должен распознать в программе каждую лексему (идентификатор, оператор, разделитель, литерал или комментарий). Иначе выполнить код просто не получится.
Синтаксические ошибки
Допустим интерпретатор Python распознал каждую лексему (они также называются токенами) в коде, но программа при этом все равно может не запуститься. Например – потому, что она содержит синтаксическую ошибку. Синтаксические сбои возникают всякий раз, когда разработчик задействует неправильную грамматику или пунктуацию (не соответствующую правилам синтаксиса языка программирования).
Пример ошибки синтаксиса в Python
print('Привет, это я)
Забыли хоть один символ? Все, программа уже не запустится.
Возвращаясь к примеру с заблудившимся автомобилистом.. Мы могли бы ответить ему: «За двести метров проехать девять только». Здесь каждое слово / токен по отдельности распознается как корректное, но мы объединили их бессмысленным и запутанным образом: части речи расположены так, что не соответствуют правилам грамматики естественного языка (русского).
Итак, если программа содержит какие-либо нераспознанные лексемы или синтаксические ошибки, то интерпретатор обнаружит их и сообщит об этом.
И при лексических, и при синтаксических ошибках интерпретатор не имеет представления о том, что мы хотели сказать, поэтому он не будет пытаться исправить такие ошибки, а просто сообщит о них.
Все вышеуказанные ошибки называются статическими, потому что интерпретатор обнаруживает их, пока мы набираем и отлаживаем программу, до ее запуска на выполнение. Ошибки, возникающие уже во время работы программы, называются динамическими (или ошибками runtime / времени выполнения).
Ошибки времени выполнения
Подобные ошибки возникают, когда интерпретатор выполняет код и обнаруживает, что не может выполнить одну из инструкций (например, необходимо выполнить деление на 0). Если интерпретатор распознает такой случай, то вызывает исключение (о них расскажем чуть позже) и завершает выполнение программы, предоставляя некоторую информацию о сбое.
Пример ошибки выполнения в С++
// C++ программа для иллюстрирования
// SIGFPE-сбой
#include <iostream>
using namespace std;
// Driver Code
int main()
{
int a = 5;
// Division by Zero
cout << a / 0;
return 0;
}
Возвращаясь к нашему автомобилисту, пытающемуся добраться из Реутова в Москву, мы можем сказать ему: «Продолжайте ехать 150 километров». Но если он ехал на запад и будет интерпретировать наши инструкции буквально, то он сможет проехать всего несколько километров, прежде чем достигнет Москвы. В этот момент наш автомобилист остановится (надеемся) и поймет, что не смог выполнить наши инструкции так, как они были даны. Этот пример иллюстрирует сбой выполнения.
Логические ошибки
Последняя категория ошибок является самой коварной: интерпретатор не может обнаружить и сообщить о логических ошибках. Логический сбой возникает всякий раз, когда интерпретатор успешно выполняет написанную программу, но это не приводит к ожидаемому результату.
Возвращаясь к нашему автомобилисту, который пытается добраться до Москвы из Реутова, мы могли бы снова сказать ему: «Просто продолжайте ехать 5 километров». Но если бы на этот раз он ехал «лицом» на юг, он мог бы успешно выполнить наши инструкции, но оказался бы в Воронеже, а не в Москве.
Пример логической ошибки
int average(int a, int b)
{
return a + b / 2; /* правильная запись (a + b) / 2 */
}
Забыли об иерархии операторов
Помните, что языки программирования не понимают ни смысл программы, ни того, что им поручено сделать. Языки программирования, грубо говоря, знают только, как запустить программу.
И часто логические сбои возникают на ранних этапах разработки программы, а затем приводят к сбоям выполнения. В таких случаях сбой может проявиться совсем не в том в месте, которое было источником первой ошибки. Таким образом, логические ошибки нужно искать особо внимательно.
Что такое ворнинг
Ворнинг (от английского warning) – это не ошибка, а только предупреждение. Такие предупреждения выводятся компилятором, когда существуют формальная вероятность возникновения сбоя.
В большинстве случаев необходимо активировать функцию treat warnings as errors – в таком случае компилятор будет помечать все найденные воринги в качестве сбоев и вы не сможете работать дальше, пока их не исправите.
Пример treat warnings as errors в SciLexer Property Pages
Лучше ещё до начала тестов внимательно изучить каждый ворнинг и попытаться проанализировать вероятность возникновения сбоя. Если устранить все ворнинги не представляется возможным, то нужно минимизировать их количество. Помните: у каждого компилятора существуют собственные сценарии ворнингов.
Совет: чем меньше ворнингов будет выдавать компилятор, тем больше вероятность что вы обнаружите настоящую ошибку.
Пример ворнинга
Создать ворнинг в Python так же просто, как создать пользовательское исключение:
Ворнинг выделен красным
Что такое исключение
Исключение (exception) в программировании – это особая ситуация, возникающая во время выполнения программы и являющаяся неожиданной или аномальной. Например, к появлению исключения приводит попытка открыть несуществующий файл или прочитать файл со сбойного носителя.
Разработчик должен предвидеть возможность возникновения исключений и правильно обрабатывать их в коде, при этом выполнение программы должно ветвиться, чтобы избежать фатального сбоя. Этот аспект программирования известен как обработка исключений.
Виды исключений
Исключения можно отнести к одному из двух типов: предопределенные и определенные.
- Предопределенные исключения являются встроенными в систему и, обычно возникают, когда разработчик пишет не соответствующий код или пытается получить несанкционированный доступ либо изменить функциональность системы.
- Определенные исключения создаются разработчиком для предупреждения пользователей программы об определенных сбоях.
Исключения попадают в обработчик исключений, который помогает базовой системе разрешить или нейтрализовать это исключение. По сути, обработчик исключений – это просто код, который выполняется после возникновения исключения. Runtime-методы просматривают стек вызовов, чтобы найти соответствующий метод, содержащий тот или иной фрагмент кода.
Пример исключения
Итак, исключения – это сбои, возникающие во время выполнения программы и нарушающие нормальный ход выполнения инструкций в программе. Например, попробуйте найти причину исключения в этом примере:
public static void Main ()
{
int numerator, denominator;
try
{
int quotient = numerator/denominator;
}
catch (DivideByZeroException e)
{
System.out.println("Divide By Zero Exception Occurred!");
}
}
От чего зависит количество ошибок
По мере усложнения кода количество сбоев, с которыми вы столкнетесь, будет расти в геометрической прогрессии.
Ошибка – это не плохо. Она лишь означает, что вы пытаетесь сделать что-то сложнее, чем обычным. И это еще не работает, как задумано. Но ошибка ни в коем случае не означает, что вы должны прекратить попытки.
И вы можете спросить: «зачем вообще тратить столько времени на разговоры о сбоях, если в идеале они должны быть минимизированы?». Ответ на этот вопрос простой: вместо того, чтобы ожидать написания полностью работающей программы, лучше сосредоточиться на написании частично работающей программы. Затем мы должны быстро обнаружить и исправить найденные ошибки, внимательно анализируя код (либо вручную, либо с помощью специальных инструментов).
При каждом изменении кода мы должны быть способны доказать, что хотя бы один сбой был устранен успешно, и программа в итоге стала более корректной. Но начинающие разработчики иногда изменяют программы необдуманно, в результате чего программа становится менее корректной. Избегайте этого подхода: отлаживайте код, тщательно анализируя его и внося только проверенные исправления, которые вам понятны.
Вот лишь некоторые компоненты, от которых зависит количество сбоев в итоговом коде:
- Используемые библиотеки.
- Используемые компиляторы.
- Используемые фреймворки.
- Особенности операционной системы.
Что касается самих сбоев, то чаще всего они относятся к:
- Дизайну программы.
- Тестированию.
- Управлению потоками.
- UI.
- Сбою системы обработки.
- GIT.
- Граничным условиям.
- Нагрузке.
- Вычислениями.
- Обработке данных.
- Интерпретации данных.
На самом деле, существуют целые инженерные профессии, построенные на поиске и исправлении сбоев в коде. Например, инженер по тестированию создает автоматизированные тесты для обнаружения ошибок в программном обеспечении и проверки его соответствия стандартам компании.
Почти все крупные технологические компании предлагают денежные вознаграждения программистам, которые могут найти сбои в их программном обеспечении.
Почему же эти гиганты платят за найденные проблемы? Почему крупная технологическая компания хочет, чтобы пользователи пытались взломать её программное обеспечение? Ответ: поиск проблем –- один из лучших способов улучшить код своего продукта.
Ошибки показывают слабые места, заставляют задуматься о том, чего вы хотите добиться от своего кода, и в итоге помогают создавать более надежные и безопасные продукты.
Два инструмента для устранения ошибок
Для снижения забагованности кода используют множество инструментов. Самые востребованные – дебаггинг и сплиты.
Отладка
Отладка или debugging – это деятельность по поиску и устранению ошибок в программе. Дебаггинг происходит сразу после обнаружения сбоев в тестах. Разработчик, тестирующий программу, всегда пытается найти bug для исправления.
Отладка – это обязательный шаг на ранних этапах разработки программы.
Ещё один инструмент локализации багов и других сбоев – модульное тестирование.
Модульное тестирование
Модульное тестирование (юнит-тестирование) делает процесс отлавливания багов более гибким. Когда вы добавляете все новые и новые функции в продукт, иногда возникает необходимость изменить старый дизайн и код. Однако менять уже протестированный код – рискованно и дорого. Если же есть модульные тесты – можно с уверенностью приступать к рефакторингу.
Другими словами, модульные тесты способствуют безопасному рефакторингу кода.
Качество кода. Юнит-тестирование улучшает качество кода. Оно выявляет все возможные дефекты до того, как код будет отправлен на интеграционное тестирование. Написание тестов до начала кодирования заставляет разработчика тщательнее обдумывать проблему. Оно выявляет сторонние эффекты и заставляет писать более качественный код.
Процесс отладки. Юнит-тестирование помогает упростить процесс отладки. Если тест не сработал, то отлаживать нужно только последние изменения, внесенные в код.
Раннее обнаружение. Проблемы обнаруживаются на ранней стадии. Поскольку модульное тестирование проводится через проверки отдельного кода перед интеграцией, проблемы могут быть обнаружены очень рано и могут быть решены сразу же, не затрагивая другие части кода.
Как найти и избежать ошибку
Вот пошаговая инструкция, как найти bug в коде и устранить его:
- Зарегистрировать bug в трекере. Это необходимо, чтобы не потерять условия появления сбоя и при необходимости воспроизвести его позже.
- Погуглите сообщение о возникшей ошибке. Большая часть сообщений уже разобрана в интернете, поэтому достаточно поискать по описанию.
- Идентифицируйте конкретную строчку, где появляется bug. В некоторых случаях, такой подход позволит обнаружить источник или природу сбоя. Например, нередко программа «падает» из-за некорректных данных. И отладчик позволит поэтапно проанализировать трассировку стека и обнаружить источник сбоя.
- Определите вид или класс ошибки. Это могут быть самые разнообразные сценарии, от неправильных состояний до переполнения буфера.
- Используйте технику исключений. Отключайте блоки до тех пор, пока баг не будет устранён. Либо можно закрывать отдельные методы при помощи фреймворка для модульного тестирования. Такое изолирование нужно делать до тех пор, пока сбой не будет устранен.
- Делайте более тщательное логирование и проверяйте каждую запись в логах.
- Попробуйте подключиться к другой сети.
- Попробуйте сменить компьютер, который используется для запуска программы. Другими словами, необходимо максимально исключить влияние и аппаратной платформы и программной.
- Анализируйте совпадения. Вы должны найти какие-либо общие моменты, которые предшествуют появлению сбоя. Может быть – это определённое время суток, определенная сеть, определённая уровень загруженности оперативной памяти.
Внимательно проанализировав каждый из вышеуказанных факторов, вы точно найдёте источник сбоя и сможете избежать его в будущем