День 09 · Отладчик и поиск ошибок · страница 6 из 6

Типовые дефекты

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

Ни один дефект со страницы компилятор не замечает. go vet на всех семи фрагментах этой страницы тоже молчит — это проверено.

В фрагментах 1–6 показаны только строки из тела main — вставляйте их между { и } в scratch/main.go. С одним import "fmt" первая строка тела — шестая. Седьмому фрагменту нужны другие пакеты, поэтому он показан программой целиком.


1. Граница цикла

Симптом: не хватает первого или последнего элемента, или один лишний.

Код для чтения · разбираем, набирать не нужно
	first := 7
	last := 10
	for shelf := first; shelf < last; shelf++ {
		fmt.Println("Проверена полка", shelf)
	}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day09/scratch$ go run .
Проверена полка 7
Проверена полка 8
Проверена полка 9

Полка 10 не проверена: < не пропускает последнее значение. Как ловить: три вопроса к заголовку из дня 08 — первое значение, последнее, сколько проходов — и запуск на одном элементе.

2. Порядок условий

Симптом: крупные значения попадают в ветку для средних.

Код для чтения · разбираем, набирать не нужно
	sum := 250000
	if sum >= 100000 {
		fmt.Println("Скидка 5%")
	} else if sum >= 200000 {
		fmt.Println("Скидка 10%")
	}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day09/scratch$ go run .
Скидка 5%

Первая истинная ветка забирает всё, до второй очередь не доходит никогда. Как ловить: подставить значение больше всех порогов. Пороги с >= идут от большего к меньшему — день 06.

3. Деление до умножения

Симптом: копейки теряются, итог чуть меньше верного.

Код для чтения · разбираем, набирать не нужно
	price := 1999
	percent := 15
	fmt.Println(price/100*percent, price*percent/100)
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day09/scratch$ go run .
285 299

1999/100 — целое деление, сразу 19: 99 копеек отброшены и уже не умножаются. Как ловить: цена, не кратная 100. Сначала умножать, делить — последним.

4. Накопитель внутри цикла

Симптом: «итог» всегда равен последнему значению.

Код для чтения · разбираем, набирать не нужно
	for day := 1; day <= 3; day++ {
		shipped := 0
		shipped += 40
		fmt.Println("День", day, "отгружено с начала недели:", shipped)
	}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day09/scratch$ go run .
День 1 отгружено с начала недели: 40
День 2 отгружено с начала недели: 40
День 3 отгружено с начала недели: 40

Как ловить: найти строку, где накопитель объявлен. Внутри цикла он обнуляется на каждом проходе; объявлять его нужно до цикла — день 08.

5. Перепутанные переменные

Симптом: правильные числа стоят не на своих местах.

Код для чтения · разбираем, набирать не нужно
	n := 3
	qty := 25
	fmt.Printf("Заказ %d: %d шт.\n", qty, n)
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day09/scratch$ go run .
Заказ 25: 3 шт.

Оба значения — целые, поэтому ни компилятор, ни go vet не возражают. Чаще всего путают похожие переменные: номер строки и счётчик, остаток и заказ. Как ловить: locals в отладчике и сверка каждой переменной с ожиданием.

6. Лишний или недостающий break

Симптом: цикл кончается раньше, чем нужно, — или находит не первое подходящее, а последнее.

Код для чтения · разбираем, набирать не нужно
	for cell := 1; cell <= 5; cell++ {
		if cell == 3 {
			fmt.Println("Ячейка 3 на ремонте")
			break
		}
		fmt.Println("Проверена ячейка", cell)
	}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day09/scratch$ go run .
Проверена ячейка 1
Проверена ячейка 2
Ячейка 3 на ремонте

Ячейки 4 и 5 не проверены: пропустить одну ячейку — это continue, а break заканчивает весь цикл. Обратный случай — break забыли: поиск «первой подходящей» не останавливается и доходит до последней. Как ловить: точка останова внутри if — сколько раз программа там остановится.

7. Строка без TrimSpace

Симптом: одинаковые на вид строки не равны.

Код для чтения · разбираем, набирать не нужно
package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		if scanner.Text() == "отмена" {
			fmt.Println("Заказ отменён")
		} else {
			fmt.Printf("Непонятная команда: %q\n", scanner.Text())
		}
	}
}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day09/scratch$ printf 'отмена\nотмена \n' | go run .
Заказ отменён
Непонятная команда: "отмена "

Без %q пробел в конце не был бы виден. Как ловить: %q в отладочной печати или print в отладчике; сравнивать strings.TrimSpace(scanner.Text()).


Сводка

Симптом Что проверить первым
не хватает крайнего элемента или есть лишний границу цикла: < или <=, с чего начали
большие значения обработаны как средние порядок условий
итог меньше на копейки деление до умножения
итог равен последнему значению где объявлен накопитель
числа верные, но не там перепутанные переменные
цикл закончился рано или нашёл последнее вместо первого break и continue
одинаковые на вид строки не равны пробелы: %q, TrimSpace

Попробуйте сейчас: bug3 и строка с пробелом.

Цель: bug3 выбирает первую подходящую ячейку, а предсказание для программы со строкой с пробелом сдано.

1. Задание bug3:

▶ Выполните
cd ~/gocourse/day09/bug3
cat TASK.txt
go run . < input.txt

2. Какой симптом из сводки подходит? Назовите гипотезу, проверьте её печатью или точкой останова, почините минимально и посмотрите правку. Проверьте форматирование — gofmt -l . ничего не печатает:

▶ Выполните
diff ~/.course/orig/day09/bug3/main.go main.go
gofmt -l .

3. Потом вставьте в ~/gocourse/day09/scratch/main.go программу целиком и, не запуская, предскажите, что она напечатает:

✎ Наберите в файл scratch/main.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	status := "отгружен "
	if status == "отгружен" {
		fmt.Println("да")
	} else {
		fmt.Println("нет")
	}
	fmt.Println(len(status), len(strings.TrimSpace(status)))
}

4. Сдайте ответ:

▶ Выполните · выделенное замените своим
course answer day09.q6 ОТВЕТ

Вместо ОТВЕТ — всё, что напечатает программа, одной строкой через пробел. len считает байты, кириллическая буква занимает два — день 04.

5. Потом запустите и сверьте с тем, что сдали:

▶ Выполните
cd ~/gocourse/day09/scratch
go run .

Готово, когда: вывод bug3 на input.txt совпадает с «ожидалось» из TASK.txt, в diff с оригиналом одна-две строки, gofmt -l . ничего не печатает, предсказание для программы со строкой с пробелом сверено запуском, а пункты bug3, q6 и bugfmt в course check 09 зелёные.


Словарик ошибок

Сообщения отладчика — такие же записи для ~/errors.md, как сообщения компилятора. Сегодня добавьте хотя бы две: например, Stdin is not a terminal, could not find statement, could not find symbol value for, has exited with status 0, expected '==', found '='. Формат прежний: текст сообщения, через тире — что оно значило у вас. Проверка считает словарик накопительно, с дня 01: к концу дня 09 в нём должно набраться минимум восемнадцать записей всего, а не восемнадцать новых. Если их уже больше — пункт зелёный, дописывать до числа не нужно.


Что может пойти не так

Что видите Что это значит Что делать
gofmt -l . печатает main.go добавленная строка без отступа или с пробелами вместо табуляции gofmt -w . или Ctrl+S в VS Code
course check: изменено строк больше лимита переписали больше, чем требует причина diff с оригиналом; начать заново: cp ~/.course/orig/day09/bug3/main.go ~/gocourse/day09/bug3/
Пример из TASK.txt сходится, а скрытый случай — нет починен симптом под свой input.txt, причина осталась вернитесь к гипотезе: какая причина, а не какое число
course check: строка 1, программа напечатала: «отладка: …» в задании осталась отладочная печать удалите её
./main.go:36:2: break is not in a loop, switch, or select break стоит после закрывающей скобки цикла break — внутрь if, в цикле

Материал дня закончен. Задания — course lab 09, дополнительное чтение — course extra 09