День 15 · Контрольная точка 3: функции, тесты и репозиторий · страница 2 из 4

Памятка блока 3

Справочник по дням 11–14, а правила трассировки — за весь курс, все двадцать четыре. Читать подряд не нужно — обязателен только блок «Попробуйте сейчас». Остальное открывайте, когда понадобится. Все сообщения — настоящие, Go 1.27.1.


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

Цель: в ~/errors.md не меньше тридцати разборов.

1. Недостающие сообщения получите сами: копируйте в черновик функцию из любой задачи и ломайте способами из таблиц ниже, по одному за раз. После каждой поломки — сборка или прогон теста:

▶ Выполните
cd ~/gocourse/day15/scratch
go build .
go test .

2. Строка словарика — текст ошибки, тире, что это значило в вашем коде. Двоеточие вместо тире не засчитывается.

3. В словарик идут не только сообщения компилятора: строка упавшего теста и ответ git — тоже ошибки, с которыми вы разбирались.

Готово, когда: пункт errors в course check 15 зелёный.


Команды

Команда Что делает День
go test . собрать пакет вместе с тестами и прогнать их 14
go test -v . то же, но с именем каждого теста 14
go test -run TestCost . прогнать только тесты, чьё имя подходит под образец 14
go test -count=1 . прогнать заново, не беря прошлый результат 14
go vet . поиск подозрительных мест, которые компилятор пропускает 03
gofmt -l . какие файлы отформатированы не по правилам 01
go doc testing.T.Errorf справка по функции, не выходя из терминала 04

Git — на третьей странице.


Функция — день 11

Код для чтения · разбираем, набирать не нужно
func boxCount(qty int, size int) int {
	return (qty + size - 1) / size
}
Часть Как называется Что важно
qty int, size int параметры внутри функции это копии переданных значений
int после скобок тип результата если он объявлен, каждый путь обязан кончаться return
boxCount(250, 24) вызов аргументы подставляются по порядку, а не по имени

Функция может стоять до или после main — порядок объявления в файле не важен. Параметр, который не использовали, ошибкой не считается, в отличие от локальной переменной.

Копия аргумента. Это самое частое недоразумение дня 11:

Код для чтения · разбираем, набирать не нужно
func addPack(qty int) int {
	qty = qty + 5
	return qty
}

func main() {
	qty := 10
	addPack(qty)
	fmt.Println(qty)
}

Печатается 10. Функция изменила свою копию; чтобы изменение вернулось, результат нужно принять: qty = addPack(qty).


Несколько результатов и error — день 12

Код для чтения · разбираем, набирать не нужно
func tariff(weight int) (string, int) {
	if weight <= 900 {
		return "малый", 110
	}
	return "крупный", 320
}

name, price := tariff(750)
_, price = tariff(750)

_ на месте результата значит «этот мне не нужен».

Код для чтения · разбираем, набирать не нужно
func parseNumber(s string) (int, error) {
	s = strings.TrimSpace(s)
	if s == "" {
		return 0, errors.New("пусто")
	}
	n, err := strconv.Atoi(s)
	if err != nil {
		return 0, fmt.Errorf("не число: %v", err)
	}
	return n, nil
}

Соглашение: ошибка идёт последней, при ошибке остальные результаты нулевые, при успехе ошибка — nil. На стороне вызова — знакомое с дня 07:

Код для чтения · разбираем, набирать не нужно
qty, err := parseNumber(scanner.Text())
if err != nil {
	fmt.Fprintln(os.Stderr, "ошибка: количество:", err)
	os.Exit(1)
}

errors.New — готовый текст, fmt.Errorf — текст с подстановкой %v и %d.


Область видимости и затенение — день 12

Переменная видна от своего объявления до конца блока { }, в котором объявлена. Параметры и локальные переменные функции снаружи не видны.

Код для чтения · разбираем, набирать не нужно
qty := 10
if qty > 0 {
	qty := qty + 5
	fmt.Println(qty)
}
fmt.Println(qty)

Печатается 15, потом 10. Внутри if объявлена новая переменная с тем же именем — она затенила внешнюю. go vet про такое молчит: строка законная. Ловится чтением — и тем, что снаружи значение «не изменилось».

Хотите Пишите
новая переменная в этом блоке x := …
изменить уже существующую x = …

Тесты — день 14

Файл кладётся рядом с программой, имя кончается на _test.go, package тот же:

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

import "testing"

func TestOrderCost(t *testing.T) {
	got := orderCost(3, 100)
	if got != 300 {
		t.Errorf("orderCost(3, 100) = %d, хотели %d", got, 300)
	}
}
Что Правило
имя файла кончается на _test.go
имя функции начинается с Test, дальше с заглавной
параметр ровно t *testing.T
t.Errorf записать провал и идти дальше
t.Fatalf записать провал и прекратить этот тест

Табличный тест — список случаев и один цикл по нему. Устройство списка (срез структур) разбирается в блоках 4–5; пока это форма для набора, в которой меняют только строки случаев:

Код для чтения · разбираем, набирать не нужно
func TestBoxCount(t *testing.T) {
	cases := []struct {
		name string
		qty  int
		size int
		want int
	}{
		{name: "ровно две коробки", qty: 48, size: 24, want: 2},
		{name: "остаток в последней", qty: 250, size: 24, want: 11},
		{name: "одна штука", qty: 1, size: 24, want: 1},
		{name: "пусто", qty: 0, size: 24, want: 0},
	}
	for _, c := range cases {
		got := boxCount(c.qty, c.size)
		if got != c.want {
			t.Errorf("%s: boxCount(%d, %d) = %d, хотели %d", c.name, c.qty, c.size, got, c.want)
		}
	}
}

Что печатает такая таблица, когда функция неверна:

Вывод · это печатает программа, набирать не нужно
--- FAIL: TestBoxCount (0.00s)
    main_test.go:20: остаток в последней: boxCount(250, 24) = 10, хотели 11
    main_test.go:20: одна штука: boxCount(1, 24) = 0, хотели 1
FAIL
FAIL	day15/scratch	0.006s
FAIL

Строка теста одна и та же — зато видно имя каждого несошедшегося случая. Ради этого имена и пишут.


Трассировка: правила 1–24

Правила копились с дня 02 и нумеруются сквозь весь курс. Здесь они собраны целиком — это тот список, по которому сверяется таблица в kt7. Новых правил контрольная не вводит.

Строки и значения (день 02, страница 4).

  1. Одна строка таблицы — одна выполненная строка кода внутри main, включая строки с печатью. С дня 11 — строка на каждую выполненную строку кода и внутри вызванных функций (правила 16–18).
  2. Значения записываются после выполнения шага.
  3. Переменной ещё нет — прочерк -. Это не ноль.
  4. То, что шаг не трогал, переписывается из строки выше: пустых клеток не бывает.
  5. Правая часть считается по предыдущей строке таблицы.
  6. Номера строк — по исходному файлу программы, без ваших вставок с отладочной печатью.

Ветвления (день 06, страница 4).

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

Циклы (день 08, страница 4).

  1. Строка for — шаг каждый раз, когда программа к ней приходит. В первый раз выполняются начальное значение и проверка, в следующие — третья часть (i++) и проверка; в таблицу идут значения после всего этого.
  2. Последний шаг со строкой for — тот, где условие ложно; он есть всегда, и переменная цикла на нём уже «за границей».
  3. После цикла переменной цикла нет — прочерк.
  4. Строки break и continue — шаги. Значения в них переписываются из строки выше. После continue следующий шаг — строка for, после break — первая строка после цикла.
  5. Шаги нумеруются подряд, номера строк повторяются. Самопроверка: строка for встречается на один раз больше, чем выполнилось тело.

Вызов функции (день 11, страница 4).

  1. Колонка «где мы» — имя функции, чью строку сейчас выполняем: main, boxes.
  2. Строка с вызовом — шаг, и она встречается в таблице дважды: уход в функцию (результата ещё нет) и возврат (присваивание доделано).
  3. Первый шаг внутри функции — строка её заголовка: на нём параметры получают копии аргументов. Самое забываемое правило.
  4. Строка return — шаг; значения на нём те же, что строкой выше.
  5. Переменные другой функции — прочерк; переменные main, пока мы внутри функции, показываются как есть и не меняются.

Одноимённые переменные и сокращённый вызов (день 12, страница 6).

  1. Одноимённые переменные — разные колонки: trips и trips в if. До объявления внутренней и после закрывающей } её блока — прочерк.
  2. Строка с вызовом функции — один шаг, если шаги внутри функции не расписываются: в таблицу идут значения вызвавшего, уже с присвоенным результатом.

Несколько функций (день 13, страница 3).

  1. Одноимённые переменные разных функций — разные колонки, и в заголовке написано, чья: total (main), total (loadPlan).
  2. Вложенный вызов раскрывают отдельной таблицей, а в первую подставляют её результат.

Самопроверки: строк с именем функции столько, сколько строк её тела выполнилось, плюс одна на заголовок; строка вызова встречается парами — уход и возврат (правило 17), кроме случая правила 22.


Ошибки компилятора

Сообщение Причина День
missing return есть путь, на котором функция кончается без return 11
not enough arguments in call to cost и строки have/want аргументов меньше, чем параметров 11
too many arguments in call to cost аргументов больше 11
cannot use name (variable of type string) as int value in argument to cost тип аргумента не тот 11
undefined: Cost опечатка в имени при вызове 11
assignment mismatch: 2 variables but cost returns 1 value приняли два результата у функции с одним 12
not enough return values и строки have/want в return меньше значений, чем объявлено 12
too many return values в return больше значений 12
declared and not used: qty объявили и не использовали (у параметров такого не бывает) 11
undefined: err err объявлена внутри if, нужна снаружи 12
undefined: testing в файле теста нет import "testing" 14
wrong signature for TestDaysLeft, must be: func TestDaysLeft(t *testing.T) параметр теста объявлен не так 14

Полные сообщения компилятор печатает в две-три строки: после первой идут have (…) и want (…) — что передали и что ждали.


Собирается, но считает не то

Признак Дефект День
изменение внутри функции «не дошло» результат не приняли: f(x) вместо x = f(x) 11
внешняя переменная осталась прежней внутри блока := создал новую, затенил внешнюю 12
при ошибке печатается и результат нет os.Exit(1) или return после сообщения 12
при ошибке результат не нулевой вернули посчитанное значение вместе с ошибкой 12
после разбиения вывод изменился в функцию уехал лишний кусок или потерялся аргумент 13
функция считает верно, а программа нет перепутан порядок аргументов при вызове 11
смешались копейки и рубли одна функция ждёт копейки, другая отдаёт рубли 13
тест зелёный на любой реализации тест ничего не сравнивает 14
тест зелёный, а программа ошибается на границе в тесте нет случая ровно на границе 14

Дальше: памятка по git — репозиторий, коммиты и что делать с ответами git — course next