Пройти курс: Ошибки

В этом уроке мы поговорим об обработке ошибок.

Заметьте, я сказал «ошибки», а не «исключения», поскольку в 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 году.

Основное внимание в нем уделяется простоте, надежности и эффективности. Он был разработан, чтобы объединить эффективность, скорость и безопасность статически типизированного и компилируемого языка с легкостью…

Посмотреть на GitHub

Оцените статью
devanswers.ru
Добавить комментарий