Чтение программы в порядке исполнения
С первого рабочего дня вам будут присылать чужой код: «посмотри, почему здесь такой итог». Запустить его получается не всегда — сервису нужны база, сеть, настройки. Поэтому нужно уметь прочитать программу и сказать, что она сделает, до запуска.
Пять правил чтения
- Начните с
import: список пакетов говорит, чем программа занята. - Читайте
mainсверху вниз. Не прыгайте глазами кPrintlnв конце: что напечатается, зависит от того, что к этому моменту в переменных. - Вызов — «сходили и вернулись»: результат встаёт на место вызова.
- Вложенные вызовы — изнутри наружу, как на первой странице.
- В переменной — последнее присвоенное значение: вызов функции её не меняет, пока результат не присвоен через
=.
Незнакомая функция — 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.gopackage 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