День 12 · Несколько результатов и ошибка как результат · страница 6 из 7

Трассировка: колонка на каждую переменную

Затенение тем и коварно, что в коде обе переменные выглядят одинаково. В таблице трассировки — не выглядят: у каждой своя колонка, и видно, что одна меняется, а другая стоит. Сегодняшняя таблица про это.


Два правила к прежним

Правила 1–20 из дней 02, 06, 08 и 11 действуют: строка if — шаг, строка с одной закрывающей } — не шаг, строка for — шаг при каждой проверке условия, после блока переменной блока нет (правила 1–15). День 11 добавил правила про вызов функции: колонка «где мы» (16), строка с вызовом встречается в таблице дважды (17), первый шаг внутри функции — строка заголовка (18), строка return — шаг (19), переменные другой функции — прочерк (20). Сегодня к ним два. Перечислять их тут приходится потому, что правила копились пять дней; целиком список лежит в памятке блока 3 — course page 15 2, раздел «Трассировка: правила 1–24».

21. Одноимённые переменные — разные колонки. Если внутри блока объявлена переменная с тем же именем, что снаружи, в таблице это две колонки. В заголовке пишется, где объявлена: trips и trips в if. Пока внутренняя не объявлена — прочерк; после закрывающей } её блока — снова прочерк, и колонка до конца таблицы пустая.

22. Строка с вызовом функции — один шаг, если шаги внутри функции не расписываются. В таблицу идут значения переменных того, кто вызвал, уже после возврата: результаты присвоены. Это сокращение — когда нужно разобраться, что происходит внутри, таблицу ведут по правилам дня 11, с колонкой «где мы».


Пример

Номера строк слева, как в VS Code:

Код для чтения · разбираем, набирать не нужно
 1  package main
 2
 3  import "fmt"
 4
 5  func half(n int) int {
 6  	return n / 2
 7  }
 8
 9  func main() {
10  	stock := 30
11  	part := half(stock)
12  	if part > 10 {
13  		part := part - 5
14  		fmt.Println("внутри:", part)
15  	}
16  	fmt.Println("снаружи:", part)
17  }
шаг строка stock part part в if
1 10 30 - -
2 11 30 15 -
3 12 30 15 -
4 13 30 15 10
5 14 30 15 10
6 16 30 15 -
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day12/scratch$ go run .
внутри: 10
снаружи: 15

Разбор по шагам:

  1. Строка 10 создаёт stock. Переменной part ещё нет — прочерк.
  2. Строка 11 — вызов half. По правилу 22 это один шаг: внутрь не идём, записываем результат, который вернулся, — 15.
  3. Строка 12 — if: условие вычисляется, значения не меняются. Шаг всё равно есть (правило 7 дня 06).
  4. Строка 13 — part := part - 5. Появляется внутренняя part: справа стоит внешняя, 15, значит внутри 10. Колонка part остаётся 15.
  5. Строка 14 — печать внутренней: 10.
  6. Строка 15 — одна закрывающая }, не шаг. Строка 16 печатает внешнюю part — 15, а внутренней уже нет: прочерк.

Последние две строки таблицы — то, что видно в выводе: внутри: 10 и снаружи: 15. Если бы в строке 13 стояло part = part - 5, колонка part в if осталась бы пустой всю таблицу, а в строке 16 напечаталось бы 10.


Попробуйте сейчас: таблица на бумаге.

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

1. Закройте таблицу выше рукой и заполните её сами — шесть шагов, три колонки со значениями.

2. Наберите ту же программу — уже без номеров строк — в scratch/main.go:

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

import "fmt"

func half(n int) int {
	return n / 2
}

func main() {
	stock := 30
	part := half(stock)
	if part > 10 {
		part := part - 5
		fmt.Println("внутри:", part)
	}
	fmt.Println("снаружи:", part)
}

Запустите:

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

3. Поменяйте в строке 13 := на = и запустите снова. Какая строка вывода изменилась и почему?

Готово, когда: ваша таблица совпала с таблицей выше, а после замены := на = вторая строка вывода стала снаружи: 10.


Проверить таблицу запуском

Отладочная печать из дня 09 работает и здесь — только печатать нужно в двух местах, до блока и внутри него, и подписывать, какая это переменная:

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

import "fmt"

func half(n int) int {
	return n / 2
}

func main() {
	stock := 30
	part := half(stock)
	fmt.Println("до блока: part", part)
	if part > 10 {
		part := part - 5
		fmt.Println("в блоке: part", part)
	}
	fmt.Println("после блока: part", part)
}
Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day12/scratch$ go run .
до блока: part 15
в блоке: part 10
после блока: part 15

Три строки читаются как три колонки таблицы: до блока внешняя part равна 15, в блоке напечатана внутренняя — 10, после блока снова внешняя, и она не изменилась. Если бы переменная была одна, вторая и третья строки были бы одинаковыми: это и есть способ отличить затенение от обычного присваивания, не читая код глазами.

Заметьте, что печать сдвинула номера строк: в этой программе if стоит в строке 13, а в исходной — в 12. Правило то же, что в днях 02 и 08: опыты — в копии, а номера строк в таблице — по исходному файлу. Копию удобно сделать одной командой:

Пример · только посмотреть, набирать не нужно
stagiaire@lab:~/gocourse/day12/trace$ cp program.go ~/gocourse/day12/scratch/main.go
stagiaire@lab:~/gocourse/day12/trace$ cd ~/gocourse/day12/scratch
stagiaire@lab:~/gocourse/day12/scratch$ go run .

Чего не видно в выводе

Печать показывает значения только там, где стоит Println. Таблица показывает их после каждой выполненной строки — и в этом её польза при разборе дефекта.

Что Видно в выводе Видно в таблице
значение после каждой строки только там, где есть печать всегда
момент, когда появилась вторая переменная никогда строка с := внутри блока
момент, когда внутренняя переменная исчезла никогда прочерк после закрывающей }
что условие if вообще проверялось только если ветка сработала отдельный шаг на строке if
что внешняя переменная не менялась если её печатают до и после колонка стоит на месте всю таблицу

Последняя строка — про сегодняшний дефект. Когда в колонке trips восемь одинаковых чисел, а в соседней колонке числа меняются, вопрос «почему после блока старое значение» отпадает сам собой.

Поэтому таблицу заполняют до запуска: она предсказание, а запуск — проверка предсказания. Если заполнять после, останется только переписать то, что и так напечаталось.


Таблица в файле

Задание trace — в том же формате, что в днях 02, 06, 08 и 11: файл

table.txt рядом с program.go, шапка-комментарий # шаг | строка | …, по строке на каждую выполненную строку main. Номера строк уже проставлены — считать нужно значения; прочерки и число строк не трогайте, меняйте только ?.

Колонок пять: stock, load, trips, rest и trips в if. Последняя — про переменную, объявленную внутри блока if (правило 21). Строка с вызовом ship — один шаг (правило 22).

Проверка сравнивает значения и порядок; пробелы и выравнивание не важны, регистр тоже. Где именно ошибка, она не говорит: это и есть задание.

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


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

Что видите Что это значит Что делать
В таблице ещё осталось знаков ?: 3 заполнено не всё найти ?: в VS Code Ctrl+F
Строк с шагами в таблице 7, а нужно 8 строку удалили или добавили переименовать table.txt, выполнить course lab 12 — появится новая заготовка
Все ? заполнены, но где-то значение не совпало ошибка в каком-то шаге пройти заново по правилам, сверить печатью в копии
в колонке trips в if стоят числа с самого первого шага внутренняя переменная появляется только на своей строке до неё — прочерк, он уже стоит в заготовке
после строки } в колонке trips в if снова число переменная блока живёт до закрывающей } прочерк
колонка trips меняется на шаге, где меняется trips в if это разные ячейки внешняя меняется только на строках без := внутри блока
в последней строке таблицы не те числа, что печатает программа где-то раньше пошло не так сверить печатью значений по шагам

Попробуйте сейчас: задание trace.

Цель: все ? в trace/table.txt заменены значениями, посчитанными без запуска. Числа у каждого свои.

1. Откройте задание:

▶ Выполните
cd ~/gocourse/day12/trace
ls

2. Откройте program.go и table.txt рядом. Заполните все ? без запуска: восемь шагов, номера строк уже стоят.

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

▶ Выполните
go run .

4. Промежуточные значения сверяйте в копии, program.go не меняйте:

▶ Выполните
cp program.go ~/gocourse/day12/scratch/main.go
cd ~/gocourse/day12/scratch
go run .

5. Таблица заполнена — закоммитьте:

▶ Выполните
cd ~/gocourse/day12
git add .
git commit -m "trace: таблица заполнена"

Готово, когда: в таблице не осталось ?, числа последней строки совпали с выводом программы, пункт trace в course check 12 зелёный.


Дальше: программа целиком, коммиты и словарик — course next