cpp

Способы поиска ошибок в программе

В предыдущих разделах мы научились обрабатывать ошибки времени выполнения. Однако, наибольшее количество времени программист затрачивает на другой тип ошибок — логические ошибки. В этом случае программа компилируется без ошибок, но результат выполнения программы не соответствует ожидаемому результату. Ситуация еще более осложняется, когда неверный результат проявляется лишь периодически, а не постоянно. Инсценировать такую же ситуацию, чтобы получить этот же неверный результат, бывает крайне сложно и занимает очень много времени. В этом разделе мы рассмотрим лишь "дедовские" (но по прежнему актуальные) способы поиска ошибок, а современные способы отладки приложений, доступные в редакторе Visual Studio Code, изучим в следующем разделе.

Первое, на что следует обратить внимание, — это форматирование кода. Начинающие программисты обычно не обращают на это никакого внимания, считая этот процесс лишним. А на самом деле зря! Компилятору абсолютно все равно, разместите вы все инструкции на одной строке или выполните форматирование кода. Однако, при поиске ошибок форматирование кода позволит найти ошибку гораздо быстрее.

Перед всеми инструкциями внутри блока должно быть расположено одинаковое количество пробелов. Обычно используется три или четыре пробела. От применения символов табуляции лучше отказаться, хотя в языке Go стиль программирования обязывает использовать именно табуляцию. На мой взгляд это не лучшее решение, достаточно посмотреть как неуклюже выглядит код с табуляцией в документации. Код должен выглядеть одинаково в любой ситуации, поэтому рекомендую использовать три пробела, а не табуляцию. Для вложенных блоков количество пробелов умножают на уровень вложенности. Если для блока первого уровня вложенности использовалось три пробела, то для блока второго уровня вложенности должно использоваться шесть пробелов, для третьего уровня — девять пробелов и т. д. Пример форматирования вложенных блоков приведен в листинге 13.3.

Листинг 13.3. Пример форматирования вложенных блоков

arr := [][]int{
   {1, 2, 3, 4},
   {5, 6, 7, 8},
}
for i := 0; i < len(arr); i++ {
   for j := 0; j < len(arr[i]); j++ {
      fmt.Print(arr[i][j], " ")
   }
   fmt.Println()
}

Длина одной строки не должна содержать более 80 символов. Если количество символов больше, то следует выполнить переход на новую строку. При этом продолжение смещается относительно основной инструкции на величину отступа или выравнивается по какому-либо элементу. Иначе приходится пользоваться горизонтальной полосой прокрутки, а это очень неудобно при поиске ошибок.

Если программа слишком большая, то следует задуматься о разделении программы на отдельные функции или структуры, которые выполняют логически законченные действия. Помните, что отлаживать отдельную функцию гораздо легче, чем "спагетти"-код. Причем прежде чем вставить функцию в основную программу ее следует протестировать в отдельном проекте, передавая функции различные значения и проверяя результат ее выполнения.

Обратите внимание на то, что форматирование кода должно выполняться при написании кода, а не во время поиска ошибок. Этим вы сократите время поиска ошибки и скорее всего заметите ошибку еще на этапе написания. Если все же ошибка возникла, то вначале следует инсценировать ситуацию, при которой ошибка проявляется. После этого можно начать поиск ошибки.

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

Функцию Println() удобно использовать для вывода промежуточных значений. В этом случае значения переменных вначале выводятся в самом начале программы (внутри функции main()) и производится проверка соответствия значений. Если значения соответствуют, то инструкция с функцией Println() перемещается на следующую строку программы и опять производится проверка и т. д. Если значения не совпали, то ошибка возникает в инструкции, расположенной перед инструкцией с функцией Println(). Если это пользовательская функция, то проверку значений производят внутри функции, каждый раз перемещая инструкцию с выводом значений. На одном из этих многочисленных этапов ошибка обычно обнаруживается. В больших программах можно логически догадаться о примерном расположении инструкции с ошибкой и начать поиск ошибки оттуда, а не с самого начала программы.

И еще раз говорю: всегда проверяйте входные данные. Если данные, передаваемые в функцию, не соответствуют ожидаемым значениям, то просто генерируйте панику:

func test(x int) int {
   if x < 0 {
      panic("X не может быть меньше нуля")
   }
   // Что-то делаем
   return x
}

При генерации паники выводится стек вызовов с указанием названий функцией и номерами строк. Эти номера строк отлично подойдут в качестве отправной точки для начала поиска ошибки.

Учебник Go (Golang)
Учебник Go (Golang) в формате PDF

Помощь сайту

ЮMoney (Yandex-деньги): 410011140483022

ПАО Сбербанк:
Счет: 40817810855006152256
Реквизиты банка:
Наименование: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК
Корреспондентский счет: 30101810500000000653
БИК: 044030653
КПП: 784243001
ОКПО: 09171401
ОКОНХ: 96130
Скриншот реквизитов

cpp