День 13 — глубже
Всё здесь необязательно. Материалы на английском помечены.
Как называют функции в самом Go (англ.)
- Effective Go, раздел Names — https://go.dev/doc/effective_go#names Короткие имена для коротких областей видимости,
MixedCapsвместо подчёркиваний, имена без повторения имени пакета:bytes.Buffer, а неbytes.BytesBuffer. - Go Code Review Comments, раздел Variable Names — https://go.dev/wiki/CodeReviewComments#variable-names Тот самый список замечаний, которые пишут на ревью в настоящих командах. Половина списка — про имена.
Спецификация: объявление функции (англ.)
- Function declarations — https://go.dev/ref/spec#Function_declarations Точное описание заголовка. Там же видно, что параметры одного типа можно перечислить через запятую:
func boxesFor(items, perBox int) int— это два параметра типаint, а не один.
Откуда взялось слово «рефакторинг» (англ.)
- Extract Function — https://refactoring.com/catalog/extractFunction.html Приём, которым вы сегодня занимались, в каталоге Мартина Фаулера. Там же — его правило: перед выносом должен быть способ проверить, что поведение не изменилось.
- Refactoring (Wikipedia) — https://en.wikipedia.org/wiki/Code_refactoring История и определение: изменение внутреннего устройства без изменения внешнего поведения.
Git: история и просмотр (англ.)
- git log — https://git-scm.com/docs/git-log Кроме
--onelineполезны--stat(какие файлы менялись) и-p(что именно менялось).
Документация к Sprintf прямо в терминале
Попробуйте сейчас: чем Sprintf отличается от Printf.
Цель: увидеть в
go doc, что одна функция печатает, а другая возвращает строку.▶ Выполнитеgo doc fmt.Sprintf go doc fmt.PrintfПрочитайте первую строку описания каждой: у
Sprintf— «returns the resulting string», уPrintf— «writes to standard output». Вот почемуrublesсо страницы 1 возвращает строку и ничего не печатает: печатать будет тот, кто её позвал.Готово, когда: вы можете сказать, что вернёт
fmt.Sprintf("%d", 5)и что напечатаетfmt.Printf("%d", 5).
Что ещё умеет diff
diff сравнивает файлы, и у него есть формы вывода поудобнее обычной.
stagiaire@lab:~/gocourse/day13/scratch$ diff -u baseline.txt after.txt
--- baseline.txt 2026-09-12 02:14:52.934255339 +0300
+++ after.txt 2026-09-12 02:15:41.118902744 +0300
@@ -1,3 +1,3 @@
Штук: 50
Коробок: 5
-Упаковка: 4.05 руб.
+Упаковка: 4.50 руб.| Ключ | Что делает |
|---|---|
-u |
показывает расхождение с соседними строками; так же выглядит git diff |
-q |
только отвечает, различаются файлы или нет |
-w |
не считает разницей количество пробелов |
-u полезен, когда вывод длинный: видно, в каком месте вывода разошлось.
Где искать ошибку: сужаем по дереву вызовов
Программа из шести функций напечатала неверное число. Читать все шесть подряд — долго и обычно бесполезно. Дерево вызовов со страницы 3 позволяет делить поиск пополам, и это тот же приём, что в дне 09, только вместо строк вы делите функции.
Порядок такой:
- Начните с
mainи найдите строку, которая печатает неверное число. Из неё видно, чей результат врёт:rubles(cost)— либоcost, либоrubles. - Проверьте функцию-лист отдельно. Позовите её в
scratchс теми же числами и сравните с посчитанным на бумаге. Лист либо виноват, либо вычеркнут из поиска целиком — вместе со всеми вызовами. - Если лист чист, поднимитесь на уровень выше. Виновата либо сама функция, которая его зовёт, либо аргументы, которые ей передали.
- Повторяйте, пока не останется одна функция. В ней уже читают построчно и, если нужно, ведут таблицу трассировки.
Пример на том же дереве со страницы 3. Упаковка напечаталась как 0.00, хотя коробок пять. В main за это отвечает rubles(cost) — под подозрением две функции из четырёх. Проверяем лист rubles на числе 405: печатает 4.05, значит, он в порядке и виноват cost. Его даёт packCost — осталась одна функция из четырёх, и в ней три строки.
Ровно для этого функции и возвращают результат, а не печатают сами: каждую можно позвать отдельно и вычеркнуть из списка подозреваемых одним сравнением. С программой из одного main так не выйдет — в ней нечего вычёркивать, кроме строк.
Мини-опыты
1. Порядок объявления функций. В scratch/main.go напишите main первой, а вызываемые функции — после неё. Запустите. Программа работает: порядок объявления функций в файле значения не имеет. Теперь так же переставьте две строки внутри main — результат изменится. Почему для функций порядок не важен, а для строк внутри функции важен?
2. Функция, которая ничего не возвращает. Наберите в scratch/main.go:
scratch/main.gopackage main
import "fmt"
func note(text string) {
fmt.Println(text)
}
func main() {
note("первый")
x := note("привет")
fmt.Println(x)
}# day13/scratch
./main.go:11:7: note("привет") (no value) used as valueФункция без результата не годится в правой части присваивания: возвращать ей нечего. Уберите строки с x — останется программа, которая печатает одно слово.
3. Сколько строк экономит вынос. Посчитайте строки в split/main.go до и после разбиения — командой wc -l ~/gocourse/day13/split/main.go.
Скорее всего, файл стал длиннее: заголовки функций и return занимают место. Короче стал main — то, что читают в первую очередь. Разбиение экономит не строки, а внимание читающего.
4. Один кусок — два места. В split после разбиения вызовите boxesFor (или как вы её назвали) ещё раз — в отдельной программе в scratch, скопировав функцию туда. Сколько строк пришлось скопировать? Это цена того, что функция получает всё нужное аргументами и не лезет в пакетные переменные.