Памятка блока 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)
}
}
}- строка
name stringи дальше — перечень полей случая: имя и числа; - каждая строка в фигурных скобках — один случай;
for _, c := range cases— цикл по случаям;c— текущий случай,c.qty— его поле;- добавить случай значит дописать одну строку.
Что печатает такая таблица, когда функция неверна:
--- 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).
- Одна строка таблицы — одна выполненная строка кода внутри
main, включая строки с печатью. С дня 11 — строка на каждую выполненную строку кода и внутри вызванных функций (правила 16–18). - Значения записываются после выполнения шага.
- Переменной ещё нет — прочерк
-. Это не ноль. - То, что шаг не трогал, переписывается из строки выше: пустых клеток не бывает.
- Правая часть считается по предыдущей строке таблицы.
- Номера строк — по исходному файлу программы, без ваших вставок с отладочной печатью.
Ветвления (день 06, страница 4).
- Строка с
ifили} else if— шаг; значения в ней те же, что строкой выше: условие только читает. - Строки
} else {и одинокая}— не шаги. - Строки невыполненных веток в таблицу не попадают: номера идут с пропусками.
- После первой сработавшей ветки остальная цепочка — не шаги.
Циклы (день 08, страница 4).
- Строка
for— шаг каждый раз, когда программа к ней приходит. В первый раз выполняются начальное значение и проверка, в следующие — третья часть (i++) и проверка; в таблицу идут значения после всего этого. - Последний шаг со строкой
for— тот, где условие ложно; он есть всегда, и переменная цикла на нём уже «за границей». - После цикла переменной цикла нет — прочерк.
- Строки
breakиcontinue— шаги. Значения в них переписываются из строки выше. Послеcontinueследующий шаг — строкаfor, послеbreak— первая строка после цикла. - Шаги нумеруются подряд, номера строк повторяются. Самопроверка: строка
forвстречается на один раз больше, чем выполнилось тело.
Вызов функции (день 11, страница 4).
- Колонка «где мы» — имя функции, чью строку сейчас выполняем:
main,boxes. - Строка с вызовом — шаг, и она встречается в таблице дважды: уход в функцию (результата ещё нет) и возврат (присваивание доделано).
- Первый шаг внутри функции — строка её заголовка: на нём параметры получают копии аргументов. Самое забываемое правило.
- Строка
return— шаг; значения на нём те же, что строкой выше. - Переменные другой функции — прочерк; переменные
main, пока мы внутри функции, показываются как есть и не меняются.
Одноимённые переменные и сокращённый вызов (день 12, страница 6).
- Одноимённые переменные — разные колонки:
tripsиtrips в if. До объявления внутренней и после закрывающей}её блока — прочерк. - Строка с вызовом функции — один шаг, если шаги внутри функции не расписываются: в таблицу идут значения вызвавшего, уже с присвоенным результатом.
Несколько функций (день 13, страница 3).
- Одноимённые переменные разных функций — разные колонки, и в заголовке написано, чья:
total (main),total (loadPlan). - Вложенный вызов раскрывают отдельной таблицей, а в первую подставляют её результат.
Самопроверки: строк с именем функции столько, сколько строк её тела выполнилось, плюс одна на заголовок; строка вызова встречается парами — уход и возврат (правило 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