Имена, порядок объявления и порядок аргументов
Функция появляется в программе один раз, а читается — каждый раз, когда кто-то встречает её вызов. Поэтому имя у неё важнее, чем у переменной: из имени должно быть понятно, что вернётся, не открывая тело.
Глагол для действия, существительное для значения
Ситуация. Одна и та же функция, три разных имени. Какое из них скажет читателю больше?
func f(qty int) int — ни о чём
func calc(qty int) int — «считает» что-то
func boxes(qty int) int — вернёт число коробокПравило, которым пользуются в Go:
| Что делает функция | Как называть | Примеры |
|---|---|---|
| возвращает значение | существительным — тем, что вернёт | boxes, cost, weight, total |
| проверяет и отвечает «да/нет» | с is, has, can |
isEmpty, hasDiscount |
| что-то делает, ничего не возвращает | глаголом | printRow, checkOrder, saveReport |
Плохие имена узнаются по признакам: f, f2, tmp, data, calc, process, doWork. Все они означают «здесь что-то происходит» — то есть ничего. Имя из одной буквы годится для параметра короткой функции (func double(n int) int), но не для самой функции.
Второй признак хорошего имени: вызов читается как фраза. fmt.Println("Склад A:", boxes(14), "кор.") читается вслух, а calc(14) приходится расшифровывать.
Имена в Go пишут вместе, без подчёркиваний, каждое следующее слово с большой буквы: printRow, isEmpty, costWithDiscount. Это не требование компилятора, а соглашение, и gofmt его не меняет.
Про первую букву. Свои функции сегодня называем с маленькой буквы. Заглавная первая буква в Go — не про красоту: она делает имя видимым из других пакетов. Пока пакет у нас один (package main), разницы нет, и программа с func Boxes(...) соберётся и заработает. Но привычка пригодится в блоке 5, где пакетов станет несколько.
Порядок объявления в файле не важен
Ситуация. Внутри main переменную нельзя использовать до того, как она объявлена: строки выполняются сверху вниз. Кажется, что с функциями так же.
package main
import "fmt"
func main() {
fmt.Println("Коробок:", boxes(12))
}
func boxes(qty int) int {
return qty / 6
}Что увидите.
stagiaire@lab:~/gocourse/day11/scratch$ go run .
Коробок: 2Как это читать. Работает. Объявления на верхнем уровне файла компилятор собирает все сразу, до того как начнёт разбираться в телах. К моменту, когда он смотрит на вызов boxes(12), имя boxes ему уже известно, где бы оно ни было написано — выше, ниже, в другом файле того же пакета.
Внутри функции правило другое и остаётся прежним:
package main
import "fmt"
func main() {
fmt.Println(qty)
qty := 5
}stagiaire@lab:~/gocourse/day11/scratch$ go run .
# day11/scratch
./main.go:6:14: undefined: qty
./main.go:7:2: declared and not used: qtyТак что порядок функций в файле выбираете вы. Обычный — сверху главное, ниже подробности: сначала main, под ним функции, которые он вызывает, и читатель идёт от общего к частному. Обратный порядок тоже встречается; важно, чтобы внутри одного файла он был один.
Чего нельзя: двух функций с одним именем в одном пакете.
package main
import "fmt"
func boxes(qty int) int {
return qty / 6
}
func boxes(qty int, size int) int {
return qty / size
}
func main() {
fmt.Println(boxes(12))
}stagiaire@lab:~/gocourse/day11/scratch$ go run .
# day11/scratch
./main.go:9:6: boxes redeclared in this block
./main.go:5:6: other declaration of boxesВторая строка показывает, где объявление было в первый раз. Разное число параметров не помогает: в Go имя занято целиком.
И ещё одно: функцию нельзя объявить внутри другой функции.
package main
import "fmt"
func main() {
func boxes(qty int) int {
return qty / 6
}
fmt.Println(boxes(12))
}stagiaire@lab:~/gocourse/day11/scratch$ go run .
# day11/scratch
./main.go:6:7: syntax error: unexpected name boxes, expected (Сообщение странное, потому что синтаксис для функции без имени внутри тела в Go есть — но это другая конструкция, и до неё мы дойдём не скоро. Помните признак: func с именем — всегда на верхнем уровне файла, без отступа.
Наконец, опечатка в имени при вызове:
package main
import "fmt"
func boxes(qty int) int {
return qty / 6
}
func main() {
fmt.Println(boxs(12))
}stagiaire@lab:~/gocourse/day11/scratch$ go run .
# day11/scratch
./main.go:10:14: undefined: boxsundefined для функции читается так же, как для переменной: «такого имени нет». Смотреть надо не на строку 10, а на объявление: сверить буква в букву.
Порядок аргументов: компилятор промолчит
Ситуация. У функции два параметра одного типа. В вызове их переставили местами.
package main
import "fmt"
func boxes(qty int, size int) int {
n := qty / size
if qty%size != 0 {
n++
}
return n
}
func main() {
qty := 40
size := 6
fmt.Println("Коробок:", boxes(size, qty))
}Что увидите.
stagiaire@lab:~/gocourse/day11/scratch$ go run .
Коробок: 1Как это читать. Правильный ответ — 7. Программа собралась, go vet молчит, ошибки нет ни одной: оба аргумента типа int, столько же, сколько параметров, — с точки зрения компилятора всё в порядке. Внутри функции просто посчиталось 6 / 40, то есть 0, плюс единица за остаток.
Совпадение имён не спасает: в main переменная называется size, в функции параметр тоже size, но аргумент size стоит первым и попадает в первый параметр qty. Имена не сопоставляются никогда — только позиции.
Это самый частый тихий дефект дня. Ловится он двумя способами: прочитать вызов вслух вместе с заголовком («boxes от количества и размера») и посчитать ответ руками на маленьком примере — 40 штук по 6 это 7 коробок, а не 1; неправдоподобный результат всегда повод посмотреть на вызов.
Защита от перестановки есть и в языке: параметры разных типов переставить не дадут — будет cannot use … as … value in argument. Но у двух int такой защиты нет.
Попробуйте сейчас: починить fix2.
Цель:
fix2печатает правдоподобное число коробок и остаток; правка — одна-две строки, больше двух проверка не примет.1. Запустите как есть и прочитайте комментарий в начале файла:
▶ Выполнитеcd ~/gocourse/day11/fix2 go run .2. Посчитайте руками, сколько коробок должно получиться на самом деле. Функцию
boxesи числа вmainне меняйте — правьте одну-две строки.3. После правки запустите снова и посмотрите, что именно изменилось:
▶ Выполнитеgo run . diff ~/.course/orig/day11/fix2/main.go main.goГотово, когда: число коробок совпало с вашим расчётом, «в последней» получилось меньше вместимости коробки, а
diffпоказывает одну-две изменённые строки — пунктыfix2иfixsmall.
Словарик ошибок
К концу дня в ~/errors.md — минимум двадцать два разбора: прежние и сегодняшние. Строка засчитывается, если в ней текст ошибки, тире с пробелами вокруг и что это значило; двоеточие не считается — оно есть в каждом сообщении компилятора. Сегодня встречались:
missing return— у функции обещан тип результата, аreturnможет не встретиться;not enough arguments in call to cost— аргументов меньше, чем параметров;too many arguments in call to cost— аргументов больше;cannot use size (variable of type string) as int value in argument to cost— тип аргумента не совпал с типом параметра;too many return valuesиnot enough return values—returnсо значением там, где результата нет, и наоборот;report() (no value) used as value— результат берут у функции, которая ничего не возвращает;undefined: boxs— опечатка в имени функции при вызове;boxes redeclared in this block— две функции с одним именем;syntax error: unexpected name boxes, expected (— объявление функции внутриmain.
Попробуйте сейчас: словарик и проверка дня.
Цель: в
~/errors.mdне меньше двадцати двух разборов, аcourse check 11показывает, что ещё осталось.1. Допишите сегодняшние ошибки — те, которые вы действительно видели у себя, — и запустите проверку:
▶ Выполнитеcourse check 112. Если пункт
errorsкрасный, посчитайте разборы так же, как их считает проверка: строка длиннее двадцати символов и с тире-разделителем.▶ Выполнитеgrep -E '.{20,}' ~/errors.md | grep -cE ' (-|—|–) 'Первый
grepотбрасывает слишком короткие строки, второй считает те, где между текстом ошибки и разбором стоит тире с пробелами вокруг. Обычныйgrep -c . ~/errors.mdпокажет больше: он считает все строки подряд, включая заголовки и заметки без разбора. Если числа расходятся, смотреть надо на строки без тире.Готово, когда: пункт
errorsвcourse check 11зелёный.
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
./main.go:10:14: undefined: boxs |
опечатка в имени функции при вызове | сверить с объявлением буква в букву |
boxes redeclared in this block и вторая строка other declaration of boxes |
две функции с одним именем в пакете | переименовать одну; разное число параметров не помогает |
syntax error: unexpected name boxes, expected ( |
func имя написано внутри main |
вынести объявление на верхний уровень, без отступа |
undefined: qty и рядом declared and not used: qty |
переменную читают выше её объявления внутри функции | объявить до первого использования |
программа считает не то, ошибок нет, оба аргумента int |
переставлены местами аргументы | сверить порядок с заголовком; проверить на примере, ответ которого известен |
функция называется calc, f, process |
имя ничего не говорит | существительное для значения, глагол для действия |
вызов работает, хотя функция объявлена ниже main |
так и задумано | порядок объявлений на верхнем уровне не важен |
Дальше: две задачи целиком — вынести повтор и посчитать стоимость с порогом — course next