День 04 · Чужой код и готовые функции · страница 6 из 7

Чтение программы в порядке исполнения

С первого рабочего дня вам будут присылать чужой код: «посмотри, почему здесь такой итог». Запустить его получается не всегда — сервису нужны база, сеть, настройки. Поэтому нужно уметь прочитать программу и сказать, что она сделает, до запуска.


Пять правил чтения

  1. Начните с import: список пакетов говорит, чем программа занята.
  2. Читайте main сверху вниз. Не прыгайте глазами к Println в конце: что напечатается, зависит от того, что к этому моменту в переменных.
  3. Вызов — «сходили и вернулись»: результат встаёт на место вызова.
  4. Вложенные вызовы — изнутри наружу, как на первой странице.
  5. В переменной — последнее присвоенное значение: вызов функции её не меняет, пока результат не присвоен через =.

Незнакомая функция — go doc пакет.Функция, а не догадка по названию.


Ситуация: табличка для полки

Программа печатает название товара в рамке из звёздочек:

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

import (
	"fmt"
	"strings"
	"unicode/utf8"
)

func main() {
	name := "  лампа "
	name = strings.TrimSpace(name)
	title := strings.ToUpper(name)
	width := utf8.RuneCountInString(title) + 4
	fmt.Println(strings.Repeat("*", width))
	fmt.Println("* " + title + " *")
	fmt.Println(strings.Repeat("*", width))
}

Трассировка с вызовами

Таблица — по правилам дня 02: шаг на каждую выполненную строку main, печать тоже; значения — после шага; что шаг не трогал, переписывается из строки выше; - — переменной ещё нет. Номера строк — от начала файла: здесь package main — строка 1, func main() { — строка 9. Когда в строке есть вызовы, записывайте, что вернула каждая функция.

шаг строка что вычисляется name title width экран
1 10 " лампа " → name " лампа " - -
2 11 TrimSpace(name) → "лампа" → name "лампа" - -
3 12 ToUpper(name) → "ЛАМПА" → title "лампа" "ЛАМПА" -
4 13 RuneCountInString(title) → 5, потом 5 + 4 "лампа" "ЛАМПА" 9
5 14 Repeat("*", 9), печать "лампа" "ЛАМПА" 9 *********
6 15 "* " + "ЛАМПА" + " *", печать "лампа" "ЛАМПА" 9 * ЛАМПА *
7 16 снова Repeat("*", 9), печать "лампа" "ЛАМПА" 9 *********

В файле table.txt, как в дне 02, та же таблица без двух текстовых столбцов. Строковые значения — в кавычках и с теми пробелами, которые в строке есть:

Образец · набирать не нужно
# шаг | строка | name | title | width
1 | 10 | "  лампа " | - | -
2 | 11 | "лампа" | - | -
3 | 12 | "лампа" | "ЛАМПА" | -
4 | 13 | "лампа" | "ЛАМПА" | 9
5 | 14 | "лампа" | "ЛАМПА" | 9
6 | 15 | "лампа" | "ЛАМПА" | 9
7 | 16 | "лампа" | "ЛАМПА" | 9

На шаге 3 name осталась маленькими буквами: ToUpper вернула новую строку, и её положили в title. В шаге 4 два действия: сначала вызов, потом сложение; с len(title) вышло бы 10 + 4 = 14, и рамка разъехалась бы.

Предсказание: три строки, в средней * ЛАМПА *, сверху и снизу по 9 звёздочек. Запуск:

Вывод · это печатает программа, набирать не нужно
*********
* ЛАМПА *
*********

Совпало. Если бы не совпало — ищите первый шаг, где ваше значение разошлось с настоящим; подсмотреть настоящее можно отладочной печатью, но в копии программы в scratch: номера строк в таблице всегда от исходного файла.

Попробуйте сейчас: len вместо RuneCountInString.

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

Наберите программу про табличку в черновик — сразу с len(title) вместо utf8.RuneCountInString(title):

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

import (
	"fmt"
	"strings"
	"unicode/utf8"
)

func main() {
	name := "  лампа "
	name = strings.TrimSpace(name)
	title := strings.ToUpper(name)
	width := len(title) + 4
	fmt.Println(strings.Repeat("*", width))
	fmt.Println("* " + title + " *")
	fmt.Println(strings.Repeat("*", width))
}

Не запуская, перепишите шаги 4–7 таблицы: какое теперь width и сколько звёздочек сверху? Запустите:

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

Компилятор сначала скажет "unicode/utf8" imported and not used: пакет больше не вызывается, уберите его из import и запустите снова.

Готово, когда: программа собирается без "unicode/utf8", а число звёздочек в первой строке вывода совпало с width из ваших шагов 4–7.


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

Что видите Что это значит Что делать
предсказание не совпало с выводом в трассировке ошибка найти первый шаг, где значение разошлось; проверить fmt.Println в копии в scratch
незнакомая функция, смысл неясен догадка по имени ненадёжна go doc пакет.Функция
теряетесь в длинной программе держите в голове слишком много таблица: столбец на переменную
ждёте в s большие буквы после ToUpper(s) s не меняется, пока нет s = ... смотреть, куда положен результат

Попробуйте сейчас: трассировка программы read.

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

Задание read — программа коллеги, у каждого свой список позиций и свой вес:

▶ Выполните
cd ~/gocourse/day04/read
cat TASK.txt
cat main.go
cat table.txt

Не запускайте её. Шаги, номера строк и прочерки в table.txt уже стоят, колонки — те же, что у переменных main. Замените значением каждый знак ?: строку — в кавычках, дробное число — так, как его печатает Println (23.5 и 3, без .0).

Потом по своей таблице ответьте на три вопроса из TASK.txt:

▶ Выполните · выделенное замените своим
course answer day04.q5 ВАШ_ОТВЕТ
course answer day04.q6 ВАШ_ОТВЕТ
course answer day04.q7 ВАШ_ОТВЕТ

Вместо каждого ВАШ_ОТВЕТ — строка вывода целиком: в q5 первая, в q6 вторая, в q7 третья. Только потом go run . и сверка. Не совпало — найдите шаг, где трассировка разошлась, поправьте таблицу и отправьте ответ заново.

Готово, когда: в table.txt не осталось знаков ? (пункт проверки trace), на все три ответа course answer ответил Принято: …, а три строки вывода go run . совпали с отправленными (пункты q5, q6, q7).


Дальше: собрать программу из кусков и починить — course next