В этом уроке мы поговорим об обработке ошибок.
Заметьте, я сказал «ошибки», а не «исключения», поскольку в Go нет обработки исключений.
Вместо этого мы можем просто вернуть встроенный тип error
, который является интерфейсным типом.
type error interface {
Error() string
}
Мы вернемся к этому вопросу в ближайшее время. Сначала попробуем понять основы.
Итак, объявим простую функцию Divide
, которая, как следует из названия, будет делить целое число a
на b
.
func Divide(a, b int) int {
return a/b
}
Отлично. Теперь мы хотим вернуть ошибку, скажем, чтобы предотвратить деление на ноль. Это подводит нас к построению ошибок.
Конструирование ошибок
Существует несколько способов сделать это, но мы рассмотрим два наиболее распространенных.
Пакет errors
Первый способ заключается в использовании функции New
, предоставляемой пакетом errors
.
package main
import "errors"
func main() {}
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a/b, nil
}
Обратите внимание, как мы возвращаем ошибку с результатом. А если ошибки нет, мы просто возвращаем nil
, так как это нулевое значение ошибки, потому что, в конце концов, это интерфейс.
Но как же нам ее обработать? Для этого давайте вызовем функцию Divide
в нашей функции main
.
package main
import (
"errors"
"fmt"
)
func main() {
result, err := Divide(4, 0)
if err != nil {
fmt.Println(err)
// Do something with the error
return
}
fmt.Println(result)
// Use the result
}
func Divide(a, b int) (int, error) {...}
$ go run main.go
cannot divide by zero
Как вы видите, мы просто проверяем, является ли ошибка nil
, и строим нашу логику соответствующим образом. Это считается довольно идиоматичным в Go, и вы увидите, как это часто используется.
Другой способ построения ошибок — использование функции fmt.Errorf
.
Эта функция похожа на fmt.Sprintf
и позволяет нам отформатировать нашу ошибку. Но вместо строки она возвращает ошибку.
Она часто используется для добавления контекста или деталей к нашим ошибкам.
...
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("cannot divide %d by zero", a)
}
return a/b, nil
}
Это должно работать аналогично.
$ go run main.go
cannot divide 4 by zero
Ожидаемые ошибки
Еще один важный прием в Go — определение ожидаемых ошибок, чтобы их можно было явно проверить в других частях кода. Такие ошибки иногда называют ошибками-дозорными.
package main
import (
"errors"
"fmt"
)
var ErrDivideByZero = errors.New("cannot divide by zero")
func main() {...}
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, ErrDivideByZero
}
return a/b, nil
}
В Go считается традиционным префикс переменной Err
. Например, ErrNotFound
.
Но какой в этом смысл?
Это становится полезным, когда нам нужно выполнить другую ветвь кода при возникновении определенного вида ошибки.
Например, теперь мы можем явно проверить, какая ошибка произошла, используя функцию errors.Is
.
package main
import (
"errors"
"fmt"
)
func main() {
result, err := Divide(4, 0)
if err != nil {
switch {
case errors.Is(err, ErrDivideByZero):
fmt.Println(err)
// Do something with the error
default:
fmt.Println("no idea!")
}
return
}
fmt.Println(result)
// Use the result
}
func Divide(a, b int) (int, error) {...}
$ go run main.go
cannot divide by zero
Пользовательские ошибки
Эта стратегия охватывает большинство случаев использования обработки ошибок. Но иногда нам нужны дополнительные возможности, например, динамические значения внутри ошибок.
Ранее мы видели, что error
— это просто интерфейс. Поэтому, по сути, что угодно может быть error
, если оно реализует метод Error()
, который возвращает сообщение об ошибке в виде строки.
Итак, давайте определим нашу пользовательскую структуру DivisionError
, которая будет содержать код ошибки и сообщение.
package main
import (
"errors"
"fmt"
)
type DivisionError struct {
Code int
Msg string
}
func (d DivisionError) Error() string {
return fmt.Sprintf("code %d: %s", d.Code, d.Msg)
}
func main() {...}
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, DivisionError{
Code: 2000,
Msg: "cannot divide by zero",
}
}
return a/b, nil
}
Здесь мы будем использовать errors.As
вместо функции errors.Is
для преобразования ошибки в правильный тип.
func main() {
result, err := Divide(4, 0)
if err != nil {
var divErr DivisionError
switch {
case errors.As(err, &divErr):
fmt.Println(divErr)
// Do something with the error
default:
fmt.Println("no idea!")
}
return
}
fmt.Println(result)
// Use the result
}
func Divide(a, b int) (int, error) {...}
$ go run man.go
code 2000: cannot divide by zero
Но в чем разница между errors.Is
и errors.As
?
Разница в том, что эта функция проверяет, имеет ли ошибка определенный тип, в отличие от Is()
, которая проверяет, является ли она конкретным объектом ошибки.
Мы также можем использовать утверждения типа, но это не является предпочтительным.
func main() {
result, err := Divide(4, 0)
if e, ok := err.(DivisionError); ok {
fmt.Println(e.Code, e.Msg) // Output: 2000 cannot divide by zero
return
}
fmt.Println(result)
}
Наконец, я скажу, что обработка ошибок в Go сильно отличается от традиционной идиомы try/catch
в других языках. Но она очень эффективна, поскольку побуждает разработчика действительно обрабатывать ошибку явным образом, что также улучшает читабельность.
Эта статья является частью моего открытого курса по Go, доступного на Github.
karanpratapsingh / go-course
Освойте основы и расширенные возможности языка программирования Go
Курс по Go
Привет, добро пожаловать на курс, и спасибо за изучение Go. Я надеюсь, что этот курс обеспечит вам отличный опыт обучения.
Этот курс также доступен на моем сайте, а также на Educative.io
Оглавление
-
Начало работы
- Что такое Go?
- Зачем изучать Go?
- Установка и настройка
-
Глава I
- Hello World
- Переменные и типы данных
- Форматирование строк
- Управление потоком данных
- Функции
- Модули
- Пакеты
- Рабочие пространства
- Полезные команды
- Построить
-
Глава II
- Указатели
- Структуры
- Методы
- Массивы и фрагменты
- Карты
-
Глава III
- Интерфейсы
- Ошибки
- Паника и восстановление
- Тестирование
- Дженерики
-
Глава IV
- Параллелизм
- Гороутины
- Каналы
- Выбрать
- Пакет синхронизации
- Расширенные шаблоны параллелизма
- Контекст
-
Приложение
- Следующие шаги
- Ссылки
Что такое Go?
Go (также известный как Golang) — это язык программирования, разработанный в Google в 2007 году и открытый в 2009 году.
Основное внимание в нем уделяется простоте, надежности и эффективности. Он был разработан, чтобы объединить эффективность, скорость и безопасность статически типизированного и компилируемого языка с легкостью…