Ошибка — это значение
Во многих языках ошибка «вылетает» сама и обрывает программу. В Go по-другому: функция, которая может не справиться, просто возвращает ошибку вторым результатом — обычным значением в обычной переменной. Что с ней делать, решает тот, кто вызвал функцию. Ничего не сделать тоже можно, и программа тогда тихо посчитает ерунду. На этой странице — как ошибку заметить и как на неё ответить.
Что лежит в err
Ситуация. Одна и та же строка кода получает то хорошее число, то мусор. Нужно понять, как выглядит err в обоих случаях.
package main
import (
"fmt"
"strconv"
)
func main() {
qty, err := strconv.Atoi("40")
fmt.Println(qty, err, err == nil)
qty, err = strconv.Atoi("сорок")
fmt.Println(qty, err, err == nil)
fmt.Println("ошибка:", err)
}Что увидите.
stagiaire@lab:~/gocourse/day07/scratch$ go run .
40 <nil> true
0 strconv.Atoi: parsing "сорок": invalid syntax false
ошибка: strconv.Atoi: parsing "сорок": invalid syntaxКак это читать. У err тип error. Значение у переменной такого типа одно из двух:
nil— ошибки нет. Сравнениеerr == nilдаётtrue;- описание проблемы.
err == nilдаётfalse, а при печати выводится текст: кто сообщает (strconv.Atoi), что делал (parsing "сорок") и что не так (invalid syntax).
Во второй строке кода =, а не :=: обе переменные уже объявлены, в них кладутся новые значения. Ошибку можно напечатать, как любую переменную, — Println сам достанет из неё текст. Пользователю такой текст полезен: в нём видна сама строка, которую не удалось разобрать, вместе с пробелами.
if err != nil
Ситуация. Если количество не число, считать сумму бессмысленно. Нужно сообщить и остановиться.
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
qty, err := strconv.Atoi(strings.TrimSpace(scanner.Text()))
if err != nil {
fmt.Println("ошибка: нужно целое число")
return
}
fmt.Println("Сумма, коп.:", qty*250)
}Что увидите.
stagiaire@lab:~/gocourse/day07/scratch$ echo abc | go run .
ошибка: нужно целое число
stagiaire@lab:~/gocourse/day07/scratch$ echo ' 12 ' | go run .
Сумма, коп.: 3000Как это читать. Трассировка для ввода abc:
| Строка | Что происходит |
|---|---|
| 12 | создан scanner |
| 13 | прочитана строка abc |
| 14 | TrimSpace вернул abc, Atoi вернул 0 и ошибку: qty = 0, err — описание |
| 15 | err != nil — true, заходим внутрь |
| 16 | печать сообщения |
| 17 | return — main закончилась, программа завершена |
Строка 19 не выполнилась. Для ввода 12 на строке 15 условие ложно, блок пропускается, выполняется строка 19. Пробелы по краям больше не мешают: их убрал TrimSpace.
!= читается «не равно»: «если ошибка не пустая». Это самая частая строка в программах на Go. Привыкайте писать её сразу под вызовом, который вернул err, — до того, как значение пошло в дело.
Без return проверка бесполезна. Уберите строку 17 — программа сообщит об ошибке и пойдёт дальше:
stagiaire@lab:~/gocourse/day07/scratch$ echo abc | go run .
ошибка: нужно целое число
Сумма, коп.: 0Человек увидит «ошибку» и тут же сумму, а другая программа, которая читает вывод, получит строку с нулём и сочтёт её настоящей.
Попробуйте сейчас: if err != nil на пяти вводах.
Цель: предсказать вывод программы с проверкой
errна разных вводах и увидеть, что безreturnпроверка бесполезна.1. Наберите в
scratch/main.goпрограмму сif err != nil:✎ Наберите в файлscratch/main.gopackage main import ( "bufio" "fmt" "os" "strconv" "strings" ) func main() { scanner := bufio.NewScanner(os.Stdin) scanner.Scan() qty, err := strconv.Atoi(strings.TrimSpace(scanner.Text())) if err != nil { fmt.Println("ошибка: нужно целое число") return } fmt.Println("Сумма, коп.:", qty*250) }2. Запустите с вводами
abc,12,12,1.5и без ввода —go run . < /dev/null. Для каждого сначала предскажите вывод:▶ Выполнитеcd ~/gocourse/day07/scratch echo abc | go run . echo 12 | go run . echo ' 12 ' | go run . echo 1.5 | go run . go run . < /dev/null3. Потом уберите
returnи повторите сabc:▶ Выполнитеecho abc | go run .Готово, когда: все пять выводов совпали с предсказаниями, а без
returnнаabcнапечатались две строки —ошибка: нужно целое числоиСумма, коп.: 0.
Почему нельзя просто не смотреть: _
Компилятор не даёт оставить err без внимания: declared and not used: err. Но есть способ его «успокоить» — выбросить второй результат знаком подчёркивания _. Он означает «здесь было значение, оно мне не нужно»:
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
qty, _ := strconv.Atoi(scanner.Text())
fmt.Println("Сумма, коп.:", qty*250)
}stagiaire@lab:~/gocourse/day07/scratch$ echo abc | go run .
Сумма, коп.: 0Программа собирается, ничего не сообщает и выдаёт сумму ноль. На складе это заказ на ноль рублей, который ушёл дальше как настоящий. Такую ошибку потом ищут днями, потому что в месте, где она случилась, не осталось никакого следа.
Правило курса: _ на месте err не пишем. Ошибка, которую «точно не может быть», всё равно проверяется — это одна строка if и одна строка сообщения.
Два числа — две проверки
Ситуация. С ввода приходят цена и количество. Каждое превращение может не получиться, и у каждого своя проверка.
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
price, err := strconv.Atoi(strings.TrimSpace(scanner.Text()))
if err != nil {
fmt.Println("ошибка: цена —", err)
return
}
scanner.Scan()
qty, err := strconv.Atoi(strings.TrimSpace(scanner.Text()))
if err != nil {
fmt.Println("ошибка: количество —", err)
return
}
fmt.Println("Стоимость, коп.:", price*qty)
}stagiaire@lab:~/gocourse/day07/scratch$ printf '250\n4\n' | go run .
Стоимость, коп.: 1000
stagiaire@lab:~/gocourse/day07/scratch$ printf '250\nчетыре\n' | go run .
ошибка: количество — strconv.Atoi: parsing "четыре": invalid syntaxВо втором := переменная err уже есть. Это разрешено: := требует, чтобы хотя бы одна переменная слева была новой. qty — новая, её объявят, а в err просто положат новое значение. Если же обе переменные слева уже существуют, получится знакомая ошибка:
package main
import (
"fmt"
"strconv"
)
func main() {
price, err := strconv.Atoi("250")
qty, err := strconv.Atoi("4")
fmt.Println(price*qty, err)
price, err := strconv.Atoi("300")
fmt.Println(price)
}stagiaire@lab:~/gocourse/day07/scratch$ go run .
# day07/scratch
./main.go:12:13: no new variables on left side of :=В строке 12 и price, и err уже объявлены — нужен =.
В сообщении об ошибке полезно назвать, что не разобралось: цена или количество. Текст из err говорит, какая строка была плохой, но не говорит, где она стояла во вводе.
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
invalid operation: err != 0 (mismatched types error and untyped int) |
err сравнили с нулём, а это не число |
err != nil |
| напечатана ошибка, а ниже ещё строка с результатом | после сообщения нет return |
return последней строкой внутри if |
программа молча печатает 0 на неправильный ввод |
ошибка выброшена через _ или не проверена |
qty, err := и if err != nil |
declared and not used: err |
ошибку получили и не проверили | if err != nil { … } под вызовом |
undefined: err |
в проверке err, а слева от := вместо него _ |
qty, err := |
no new variables on left side of := |
обе переменные слева уже объявлены | = вместо := |
strconv.Atoi: parsing "": invalid syntax |
строка пустая: пустая строка во вводе или ввода не было | сообщение об ошибке — это правильная реакция |
Попробуйте сейчас: задание cost.
Цель: первая программа с проверками —
cost/main.goчитает цену и количество и печатает стоимость в рублях и копейках.1. Прочитайте условие:
▶ Выполнитеcd ~/gocourse/day07/cost cat TASK.txt2. Создайте в этой папке
main.go: две строки ввода — цена в копейках и количество, одна строка вывода — стоимость в рублях и копейках. Как разложить копейки на рубли и копейки и как напечатать05, вы видели в дне 03:/,%и%02dвPrintf.3. Запустите на двух вводах:
▶ Выполнитеprintf '1250\n3\n' | go run . printf ' 5 \n 1\n' | go run .Второй запуск должен дать:
Вывод · это печатает программа, набирать не нужноСтоимость: 0 руб. 05 коп.Проверка подаст программе и другие вводы — те, которых в условии нет.
Готово, когда: второй запуск напечатал ровно эту строку, и в
course check 07пункт «cost печатает стоимость позиции по цене и количеству с ввода» (cost) отмечен ✔.
Дальше: код возврата и поток ошибок — course next