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

Отладочная печать

Самый старый способ сузить место дефекта — временно добавить в программу печать промежуточных значений. Он работает везде: на ноутбуке, на сервере, где отладчика нет, в программе, которая работает сутками. Правил два: печатать в поток ошибок и после работы убирать.

Блоки этой страницы с подписью «✎ Наберите в файл priemka/main.go» — это строки, которые вы дописываете в программу руками; куда именно, сказано в тексте перед блоком. Номера строк на страницах 3–4 считаются по оригиналу, поэтому перед ними программу вернём: cp ~/.course/orig/day09/priemka/main.go .


Печать в поток ошибок

Ситуация. В priemka нужно увидеть boxes и total после каждого прохода. Но обычный вывод программы — её результат. Его читает человек, его сохраняют в файл, его сверяет проверка курса. Если подмешать к нему отладочные строки, результат испортится.

В дне 07 у программы появился второй выход — поток ошибок, os.Stderr. На экране он виден так же, как обычный вывод, но командная строка умеет разделять их. Отладочную печать отправляют туда.

Добавьте в priemka/main.go одну строку — после total += qty, перед закрывающей скобкой цикла:

✎ Наберите в файл priemka/main.go
		fmt.Fprintln(os.Stderr, "отладка: строка", i, "qty", qty, "total", total)

Пакет "os" в этой программе уже подключён. Слово отладка: в начале — метка: по ней отладочные строки легко найти и удалить.

Что увидите.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day09/priemka$ go run . < input.txt
отладка: строка 1 qty 12 total 12
отладка: строка 2 qty 8 total 20
отладка: строка 3 qty 20 total 40
отладка: строка 5 qty 15 total 55
отладка: строка 6 qty 9 total 64
отладка: строка 8 qty 11 total 75
отладка: строка 9 qty 30 total 105
отладка: строка 10 qty 6 total 111
Коробок: 8
Штук: 111

Как это читать. Сравните с таблицей ожиданий со страницы 1. Строки 1–3 совпали. Строки 4 нет — так и задумано: пустая строка пропускается. После строки 6 total равен 64 — совпало. Следующая отладочная строка — сразу восьмая, и total в ней 75, а по таблице после восьмой должно быть 89: седьмая строка ввода прошла мимо total += qty. Первое расхождение — седьмая строка ввода.


Разделить потоки

Отладочные строки мешают читать результат. Командная строка может отправить поток ошибок отдельно: 2> — стрелка с номером потока. Номер 1 — обычный вывод, номер 2 — поток ошибок.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day09/priemka$ go run . < input.txt 2>/dev/null
Коробок: 8
Штук: 111
stagiaire@lab:~/gocourse/day09/priemka$ go run . < input.txt 2> debug.txt
Коробок: 8
Штук: 111
stagiaire@lab:~/gocourse/day09/priemka$ head -3 debug.txt
отладка: строка 1 qty 12 total 12
отладка: строка 2 qty 8 total 20
отладка: строка 3 qty 20 total 40

/dev/null — особый файл: всё, что в него записано, пропадает. 2> debug.txt сохраняет отладку в файл — удобно, когда строк сотни: файл читают less или ищут в нём строку через grep.

Если напечатать отладку обычным fmt.Println("отладка: строка", i, "total", total), разделить уже не получится — 2>/dev/null её не уберёт:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day09/priemka$ go run . < input.txt 2>/dev/null | head -4
отладка: строка 1 total 12
отладка: строка 2 total 20
отладка: строка 3 total 40
отладка: строка 5 total 55

| head -4 оставляет первые четыре строки вывода.


Попробуйте сейчас: отладка в поток ошибок.

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

1. Добавьте отладочную строку в priemka и запустите тремя способами: без 2>, с 2>/dev/null и с 2> debug.txt. Посмотрите debug.txt:

▶ Выполните
cd ~/gocourse/day09/priemka
go run . < input.txt
go run . < input.txt 2>/dev/null
go run . < input.txt 2> debug.txt
cat debug.txt

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

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

import (
	"fmt"
	"os"
)

func main() {
	total := 0
	for i := 1; i <= 3; i++ {
		total += i * 10
		fmt.Fprintln(os.Stderr, "отладка:", i, total)
	}
	fmt.Println("Итого", total)
}

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

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

Вместо ОТВЕТ — всё, что осталось на экране, одной строкой.

4. Потом запустите и сверьте:

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

Готово, когда: в priemka с 2>/dev/null на экране только Коробок: и Штук:, отладочные строки — в debug.txt, а пункты q2 и t_stderr в course check 09 зелёные. t_stderr — след команды: он смотрит, что вы запускали программу с 2>, а не что в ней была отладочная печать.


Кавычки показывают пробелы: %q

Сужение показало место — седьмая строка ввода. Гипотеза со страницы 1: Atoi не понимает текст этой строки. Проверим печатью, что именно лежит в line.

У Printf есть глагол %q: он печатает строку в кавычках, и пробелы по краям и пустая строка становятся видны. Уберите прежнюю отладочную строку и добавьте новую сразу после line := scanner.Text():

✎ Наберите в файл priemka/main.go
		fmt.Fprintf(os.Stderr, "отладка: строка %d %q\n", i, line)

fmt.Fprintf — тот же Printf, только первым аргументом указано, куда печатать. \n в конце — перевод строки: Printf сам его не ставит.

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day09/priemka$ go run . < input.txt
отладка: строка 1 "12"
отладка: строка 2 "8"
отладка: строка 3 "20"
отладка: строка 4 ""
отладка: строка 5 "15"
отладка: строка 6 "9"
отладка: строка 7 "14 "
отладка: строка 8 "11"
отладка: строка 9 "30"
отладка: строка 10 "6"
Коробок: 8
Штук: 111

"" — четвёртая строка пустая. "14 " — в седьмой после числа пробел. Можно напечатать и саму ошибку от Atoi. Уберите строку с %q и добавьте печать внутрь if err != nil, перед continue:

✎ Наберите в файл priemka/main.go
			fmt.Fprintln(os.Stderr, "отладка: строка", i, "ошибка:", err)
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day09/priemka$ go run . < input.txt
отладка: строка 4 ошибка: strconv.Atoi: parsing "": invalid syntax
отладка: строка 7 ошибка: strconv.Atoi: parsing "14 ": invalid syntax
Коробок: 8
Штук: 111

Гипотеза подтверждена: Atoi получает "14 " и отвечает invalid syntax — «неверная запись числа».


Починка и уборка

Причина — пробел. Починка — строка 20: Atoi получает строку без пробелов по краям.

✎ Наберите в файл priemka/main.go
		qty, err := strconv.Atoi(strings.TrimSpace(line))

TrimSpace — из пакета strings, а его в import этой программы нет. Блок import целиком становится таким:

✎ Наберите в файл priemka/main.go
import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

Теперь уберите всю отладочную печать и прогоните исходный ввод:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day09/priemka$ go run . < input.txt
Коробок: 9
Штук: 125

Затем — остальные вводы, на которых вы запускали программу раньше.

Почему отладочную печать убирают:

Что изменилось по сравнению с оригиналом:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day09/priemka$ diff ~/.course/orig/day09/priemka/main.go main.go
9a10
> 	"strings"
20c21
< 		qty, err := strconv.Atoi(line)
---
> 		qty, err := strconv.Atoi(strings.TrimSpace(line))

9a10 — после строки 9 оригинала добавлена строка, в новом файле она десятая. 20c21 — строка 20 изменилась и стала 21-й. Строки с < — как было, с > — как стало. Отладочных строк в diff нет — значит, уборка сделана.


Попробуйте сейчас: починить priemka, затем bug1.

Цель: priemka считает верно и без отладочной печати, bug1 считает скидку верно.

1. Почините priemka, уберите отладку, прогоните, посмотрите diff:

▶ Выполните
cd ~/gocourse/day09/priemka
go run . < input.txt
diff ~/.course/orig/day09/priemka/main.go main.go

2. Затем задание bug1:

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

3. Пройдите шаги страницы 1. Составьте ожидание для каждой позиции на бумаге, напечатайте в поток ошибок промежуточные значения и найдите первую позицию, где они расходятся. Почините минимально, уберите отладку, прогоните снова и посмотрите правку:

▶ Выполните
go run . < input.txt
diff ~/.course/orig/day09/bug1/main.go main.go

Готово, когда: priemka печатает Коробок: 9 и Штук: 125, а в diff нет отладочных строк (пункт t_diff); вывод bug1 совпадает с «ожидалось» из TASK.txt, и в diff с оригиналом bug1 — одна-две изменённые строки: и то и другое смотрит пункт bug1. Формат сверит bugfmt — вместе с bug2 и bug3.


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

Что видите Что это значит Что делать
./main.go:7:15: undefined: os отладочная печать использует os, а в import его нет добавьте "os" в import
./main.go:20:28: undefined: strings TrimSpace добавили, а пакет не подключили добавьте "strings" в import
Отладочные строки не пропадают при 2>/dev/null печать через fmt.Println — в обычный вывод fmt.Fprintln(os.Stderr, …)
course check пишет: строка 1, программа напечатала: «отладка: …» в задании осталась отладочная печать удалите строки с меткой отладка:
Номера строк в сообщениях не совпадают со страницей добавленные строки сдвинули всё, что ниже для страниц 3–4 верните оригинал: cp ~/.course/orig/day09/priemka/main.go ~/gocourse/day09/priemka/

Дальше: отладчик dlv — course next