Обработка ошибок является важным аспектом разработки программного обеспечения, и Go не является исключением. В этой статье серии я собираюсь показать вам обзор довольно уникального подхода Go к обработке ошибок.

В Go ошибки представлены типом error, который представляет собой встроенный интерфейс с единственным методом: Error() string. Этот метод возвращает строку с описанием ошибки. Важно отметить, что вы должны использовать значение nil для обозначения отсутствия ошибки.
Теперь давайте посмотрим на пример того, как вы можете создать ошибку:

func div(x, y int) (int, error) {
    if y == 0 {
        return 0, fmt.Errorf("cannot divide by zero")
    }
    return x / y, nil
}

Здесь функция div принимает два целых числа и возвращает их частное. Если второй аргумент равен нулю, функция возвращает ошибку с сообщением 'нельзя делить на ноль'. В противном случае возвращается частное и ошибка nil, указывающая на отсутствие ошибки.

Чтобы создать собственные ошибки, вы можете использовать функцию errors.New в верхней части пакета, чтобы ошибка не создавалась каждый раз при вызове функции:

import (
 "errors"
)

errDivideByZero := errors.New("cannot divide by zero")

func div(x, y int) (int, error) {
    if y == 0 {
        return 0, errDivideByZero
    }
    return x / y, nil
}

Чтобы проверить наличие ошибок в Go, вы можете использовать оператор if с переменной err. Посмотрите на пример того, как вы можете использовать функцию div:

result, err := div(10, 2)
if err != nil {
    fmt.Println(err)
    return
}

fmt.Println(result)

Также часто можно увидеть обработку ошибок в Go с помощью ключевого слова defer, которое позволяет вам отложить выполнение функции до тех пор, пока окружающая функция не вернет результат:

f, err := os.Open("file.txt")
if err != nil {
    fmt.Println(err)
    return
}
defer f.Close()

В приведенном выше примере функция Open вызывается для открытия файла 'file.txt'. Если есть ошибка, она выводится на экран, и функция возвращается. В противном случае файл открывается, и функция Close откладывается до тех пор, пока не вернется окружающая функция. Это гарантирует, что файл всегда будет закрыт, даже если есть ошибка.

Есть также несколько других способов обработки ошибок в Go, например, использование функций panic и recover для обработки ошибок времени выполнения. Как и в большинстве случаев, панику следует использовать только в main.go, рекомендуется изящная обработка ошибок.

Иногда вы можете захотеть обернуть ошибку дополнительным контекстом, прежде чем возвращать ее. Вы можете сделать это с помощью функции fmt.Errorf, которая позволяет создать новую ошибку с отформатированной строкой. Например:

_, err := os.Stat("file.txt")
if os.IsNotExist(err) {
    return fmt.Errorf("file not found: %w", err)
}

В этом коротком фрагменте функция Stat используется для проверки существования файла 'file.txt'. Если файл не существует, функция IsNotExist возвращает true, и возвращается новая ошибка с сообщением 'файл не найден' и исходная ошибка в качестве причины.

В некоторых случаях вы можете захотеть вернуть ошибку из функции, если она не может выполнить свою задачу из-за ошибки, возникшей при вызове другого метода. Вы можете сделать это с помощью функции errors.Wrap, которая позволяет обернуть ошибку дополнительным контекстом:

_, err := os.Open("file.txt")
if err != nil {
    return errors.Wrap(err, "failed to open file")
}

Приведенный выше пример может быть полезен для предоставления большего контекста при отладке ошибки.

Вы можете создать свои собственные типы ошибок с дополнительными методами или полями, создав новый тип, реализующий интерфейс error. Например:

type DivisionByZeroError struct {
    message string
}

func (e *DivisionByZeroError) Error() string {
    return e.message
}

func div(x, y int) (int, error) {
    if y == 0 {
        return 0, &DivisionByZeroError{"division by zero"}
    }
    return x / y, nil
}

Здесь тип DivisionByZeroError определяется полем message и методом Error, который возвращает сообщение об ошибке. Функция div возвращает DivisionByZeroError, если второй аргумент равен нулю, в противном случае она возвращает частное и ошибку nil.

Переключатели типов могут быть полезны для обработки ошибок, которые имеют дополнительную информацию или настраиваемое поведение за пределами основного интерфейса error. Они позволяют по-разному обрабатывать разные типы ошибок и предпринимать соответствующие действия в зависимости от конкретного типа error:

func handleErrors(err error) {
  switch v := err.(type) {
  case MyError:
    fmt.Printf("MyError: %v\n", v)
  case *MyError:
    fmt.Printf("*MyError: %v\n", v)
  default:
    fmt.Printf("Other error: %v\n", v)
  }
}

Здесь функция handleError() принимает значение error в качестве аргумента и использует переключатель типа для обработки различных типов ошибок. Тип MyError — это настраиваемый тип ошибки, который реализует интерфейс error. Переключатель типа обрабатывает как конкретный тип MyError, так и тип указателя *MyError. Если значение ошибки не относится ни к одному из этих типов, оно обрабатывается в случае default.

Плюсы и минусы

В Go использование типа error как первоклассного значения обеспечивает более гибкую обработку ошибок, чем языки, основанные на исключениях: модель обработки ошибок использует план на случай отказа. , а не успех». Кроме того, отсутствие исключений может упростить анализ поведения приложения Go, поскольку ясно, какие ошибки ожидаются и как они обрабатываются.
Однако есть и другая сторона медали, когда речь заходит о том, как реализована обработка ошибок в Go. Как упоминалось выше, в Go нет исключений, поэтому ошибки необходимо явно проверять и обрабатывать. Это может привести к повторной проверке ошибок, что сделает код менее удобным для сопровождения. Более того, тип error является интерфейсом, поэтому он требует использования утверждений типа или переключателей типа для доступа к базовому значению ошибки. Это может быть довольно многословно.

Это обертка

Уникальная модель обработки ошибок Go широко обсуждается в инженерном сообществе, но, по моему мнению, явная природа обработки ошибок и отслеживание ошибок в первую очередь — это надежный способ повысить надежность. Приложения.

В следующей статье из серии Начало работы с Go мы рассмотрим тему параллелизма.

Вы можете узнать больше о Evendyne здесь.