Ошибка как результат своей функции
В дне 07 вы приняли ошибку от чужой функции: strconv.Atoi возвращает число и error, а вы проверяли if err != nil. Тогда это выглядело особенностью Atoi. На самом деле это обычная функция с двумя результатами — ровно такая, какую вы научились писать на первой странице. Значит, свои функции тоже могут сообщать о неудаче — и должны, если могут не справиться.
Функция, которая может не справиться
Ситуация. На приёмке товар считают вручную и записывают количество строкой. В строке бывает что угодно: пусто, семь, 0, -3. Нужна функция, которая либо отдаст нормальное количество, либо честно скажет, что не смогла.
package main
import (
"errors"
"fmt"
"strconv"
"strings"
)
func parseQty(s string) (int, error) {
text := strings.TrimSpace(s)
if text == "" {
return 0, errors.New("пустое количество")
}
qty, err := strconv.Atoi(text)
if err != nil {
return 0, err
}
if qty <= 0 {
return 0, errors.New("количество должно быть больше нуля")
}
return qty, nil
}
func main() {
fmt.Println(parseQty(" 12 "))
fmt.Println(parseQty(""))
fmt.Println(parseQty("12шт"))
fmt.Println(parseQty("0"))
}Что увидите.
stagiaire@lab:~/gocourse/day12/scratch$ go run .
12 <nil>
0 пустое количество
0 strconv.Atoi: parsing "12шт": invalid syntax
0 количество должно быть больше нуляКак это читать. В заголовке два результата: (int, error). Тип error — тот же самый, что у err из дня 07; ничего нового объявлять не нужно, он встроен в язык.
errors.New("текст")создаёт ошибку с готовым текстом. Пакетerrorsподключается вimport, какstringsилиstrconv.return 0, errors.New(…)— «результата нет, вот причина».return qty, nil— «всё хорошо, ошибки нет».nilна месте ошибки означает ровно то же, что в дне 07: ошибки не случилось.- Ошибку от
Atoiможно не переписывать своими словами, а вернуть как есть:return 0, err. Она уже содержит и строку, и причину.
Обратите внимание на порядок проверок: сначала пустая строка, потом разбор числа, потом смысл числа. Каждая проверка заканчивается return — до следующей дело не доходит. Так функция читается сверху вниз списком условий, при которых работать нельзя, и последней строкой — нормальным результатом.
Соглашение, которое соблюдают все
В Go нет правила языка о том, где стоит ошибка и что лежит в остальных результатах. Есть соглашение, которому следует вся стандартная библиотека и весь чужой код, который вам достанется:
- Ошибка — последний результат.
(int, error), а не(error, int). - Если ошибка не
nil, остальные результаты бессмысленны — обычно нулевые:0,"",false. Брать их нельзя. - Если ошибка
nil, результат настоящий.
Второй пункт объясняет, почему в parseQty везде return 0, …. Можно было бы вернуть то, что успело разобраться, — но тогда вызывающий, забывший проверить ошибку, получил бы правдоподобный мусор. Ноль в такой ситуации хотя бы заметнее.
Соблюдать соглашение нужно даже там, где компилятор не мешает нарушить. Вот функция, которая возвращает ошибку первой, — она прекрасно собирается:
func parseQty(s string) (error, int) {
if s == "" {
return errors.New("пустое количество"), 0
}
return nil, len(s)
}Компилятор доволен, а любой, кто будет вызывать её по привычке как qty, err := parseQty(s), получит либо ошибку компиляции, либо — что хуже — работающую программу с перепутанными смыслами. Пишите ошибку последней.
Если же перепутать порядок в return, оставив правильный заголовок, компилятор всё-таки вмешается:
package main
import (
"errors"
"fmt"
)
func parseQty(s string) (int, error) {
if s == "" {
return errors.New("пустое количество"), 0
}
return len(s), nil
}
func main() {
fmt.Println(parseQty(""))
}# day12/scratch
./main.go:10:10: cannot use errors.New("пустое количество") (value of interface type error) as int value in return statement
./main.go:10:59: cannot use 0 (constant of type int) as error value in return statement: int does not implement error (missing method Error)Два сообщения на одну строку: первое значение не подходит под int, второе — под error. Слова interface type и missing method Error разберём в блоке 5; пока читайте их как «ошибка — не число, число — не ошибка».
Попробуйте сейчас: что вернёт своя функция.
Цель: предсказать оба результата функции с ошибкой и сдать ответ.
1. Прочитайте программу. Что напечатает
fmt.Println(qty, err != nil)?Код для чтения · разбираем, набирать не нужноpackage main import ( "errors" "fmt" "strconv" ) func parseQty(s string) (int, error) { qty, err := strconv.Atoi(s) if err != nil { return 0, errors.New("не число") } return qty, nil } func main() { qty, err := parseQty("12шт") fmt.Println(qty, err != nil) }2. Сдайте ответ — число и слово через пробел, ровно как напечатает программа:
▶ Выполните · выделенное замените своимcourse answer day12.q3 ЧИСЛО СЛОВО3. Проверьте запуском — наберите ту же программу в
scratch/main.go:✎ Наберите в файлscratch/main.gopackage main import ( "errors" "fmt" "strconv" ) func parseQty(s string) (int, error) { qty, err := strconv.Atoi(s) if err != nil { return 0, errors.New("не число") } return qty, nil } func main() { qty, err := parseQty("12шт") fmt.Println(qty, err != nil) }▶ Выполнитеcd ~/gocourse/day12/scratch go run .Готово, когда: пункт
q3вcourse check 12зелёный.
errors.New и что у ошибки внутри
stagiaire@lab:~/gocourse/day12/scratch$ go doc errors.New
package errors // import "errors"
func New(text string) error
New returns an error that formats as the given text. Each call to New
returns a distinct error value even if the text is identical.«Каждый вызов возвращает отдельное значение, даже если текст тот же» — из этого следует важное: сравнивать ошибки между собой знаком == бесполезно, две ошибки с одинаковым текстом не равны. Как проверять, «та ли это ошибка», разберём в блоке 5. Пока ошибку проверяют одним способом — err != nil.
Печатается ошибка как обычное значение: Println и %v в Printf берут из неё текст.
package main
import (
"errors"
"fmt"
)
func check(qty int) error {
if qty <= 0 {
return errors.New("количество должно быть больше нуля")
}
return nil
}
func main() {
err := check(5)
fmt.Println(err, err == nil)
err = check(0)
fmt.Println(err, err == nil)
fmt.Println("ошибка:", err)
}stagiaire@lab:~/gocourse/day12/scratch$ go run .
<nil> true
количество должно быть больше нуля false
ошибка: количество должно быть больше нуляЗдесь у функции один результат — только ошибка. Так пишут проверки, у которых нет полезного значения: «годится или нет». Слово error в заголовке стоит там же, где раньше стоял int.
Текст ошибки пишут с маленькой буквы и без точки в конце: он часто попадает в середину другого сообщения — как во второй строке вывода на следующей странице.
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
assignment mismatch: 1 variable but parseQty returns 2 values |
функция с ошибкой вызвана как обычная | qty, err := parseQty(…) |
declared and not used: err |
ошибку получили и ни разу не проверили | if err != nil { … } сразу под вызовом |
cannot use errors.New(…) (value of interface type error) as int value in return statement |
значения в return стоят в порядке, обратном заголовку |
сначала результат, ошибка последней |
invalid operation: err != 0 (mismatched types error and untyped int) |
ошибку сравнивают с нулём | err != nil |
cannot convert "" (untyped string constant) to type interface{Error() string} |
ошибку сравнивают с пустой строкой | err != nil |
undefined: errors |
пакет не подключён | "errors" в import |
| функция вернула ошибку, а программа посчитала результат | результат взяли, не посмотрев на ошибку | при ошибке результат не использовать |
ошибка «теряется»: в main всё хорошо, хотя внутри было плохо |
функция напечатала сообщение вместо return ошибки |
возвращать ошибку, решает вызывающий |
Дальше: что делает с ошибкой тот, кто вызвал функцию — course next