Golang возврат ошибок

  1. Documentation

  2. Tutorials

  3. Return and handle an error

Handling errors is an essential feature of solid code. In this section, you’ll
add a bit of code to return an error from the greetings module, then handle it
in the caller.

  1. In greetings/greetings.go, add the code highlighted below.

    There’s no sense sending a greeting back if you don’t know who to greet.
    Return an error to the caller if the name is empty. Copy the following
    code into greetings.go and save the file.

    package greetings
    
    import (
        "errors"
        "fmt"
    )
    
    // Hello returns a greeting for the named person.
    func Hello(name string) (string, error) {
        // If no name was given, return an error with a message.
        if name == "" {
            return "", errors.New("empty name")
        }
    
        // If a name was received, return a value that embeds the name
        // in a greeting message.
        message := fmt.Sprintf("Hi, %v. Welcome!", name)
        return message, nil
    }
    

    In this code, you:

    • Change the function so that it returns two values: a
      string and an error. Your caller will check
      the second value to see if an error occurred. (Any Go function can
      return multiple values. For more, see
      Effective Go.)
    • Import the Go standard library errors package so you can
      use its
      errors.New function.
    • Add an if statement to check for an invalid request (an
      empty string where the name should be) and return an error if the
      request is invalid. The errors.New function returns an
      error with your message inside.
    • Add nil (meaning no error) as a second value in the
      successful return. That way, the caller can see that the function
      succeeded.
  2. In your hello/hello.go file, handle the error now returned by the
    Hello function, along with the non-error value.

    Paste the following code into hello.go.

    package main
    
    import (
        "fmt"
        "log"
    
        "example.com/greetings"
    )
    
    func main() {
        // Set properties of the predefined Logger, including
        // the log entry prefix and a flag to disable printing
        // the time, source file, and line number.
        log.SetPrefix("greetings: ")
        log.SetFlags(0)
    
        // Request a greeting message.
        message, err := greetings.Hello("")
        // If an error was returned, print it to the console and
        // exit the program.
        if err != nil {
            log.Fatal(err)
        }
    
        // If no error was returned, print the returned message
        // to the console.
        fmt.Println(message)
    }
    

    In this code, you:

    • Configure the
      log package to
      print the command name («greetings: «) at the start of its log messages,
      without a time stamp or source file information.
    • Assign both of the Hello return values, including the
      error, to variables.
    • Change the Hello argument from Gladys’s name to an empty
      string, so you can try out your error-handling code.
    • Look for a non-nil error value. There’s no sense continuing
      in this case.
    • Use the functions in the standard library’s log package to
      output error information. If you get an error, you use the
      log package’s
      Fatal function
      to print the error and stop the program.
  3. At the command line in the hello directory, run hello.go to
    confirm that the code works.

    Now that you’re passing in an empty name, you’ll get an error.

    $ go run .
    greetings: empty name
    exit status 1
    

That’s common error handling in Go: Return an error as a value so the caller
can check for it.

Next, you’ll use a Go slice to return a randomly-selected greeting.

< Call your code from another module
Return a random greeting >

Привет, хабровчане! Уже сегодня в ОТУС стартует курс «Разработчик Golang» и мы считаем это отличным поводом, чтобы поделиться еще одной полезной публикацией по теме. Сегодня поговорим о подходе Go к ошибкам. Начнем!

Освоение прагматической обработки ошибок в вашем Go-коде

Этот пост является частью серии «Перед тем как приступать к Go», где мы исследуем мир Golang, делимся советами и идеями, которые вы должны знать при написании кода на Go, чтобы вам не пришлось набивать собственные шишки.

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

Теперь, когда мы расчистили себе путь, поехали!

Подход Go к обработке ошибок — одна из самых спорных и неправильно используемых фич. В этой статье вы узнаете подход Go к ошибкам, и поймете, как они работают “под капотом”. Вы изучите несколько различных подходов, рассмотрите исходный код Go и стандартную библиотеку, чтобы узнать, как обрабатываются ошибки и как с ними работать. Вы узнаете, почему утверждения типа (Type Assertions) играют важную роль в их обработке, и увидите предстоящие изменения в обработке ошибок, которые планируется ввести в Go 2.

Вступление

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

Если по какой-то причине ваша функция может дать сбой, вам, вероятно, следует вернуть из нее предварительно объявленный error-тип. По соглашению, возврат ошибки сигнализирует вызывающей стороне о проблеме, а возврат nil не считается ошибкой. Таким образом, вы дадите вызывающему понять, что возникла проблема, и ему нужно разобраться с ней: кто бы ни вызвал вашу функцию, он знает, что не должен полагаться на результат до проверки на наличие ошибки. Если ошибка не nil, он обязан проверить ее и обработать (логировать, возвращать, обслуживать, вызвать какой-либо механизм повторной попытки/очистки и т. д.).


(3 // обработка ошибки
5 // продолжение)

Эти фрагменты очень распространены в Go, и некоторые рассматривают их в качестве шаблонного кода. Компилятор рассматривает неиспользуемые переменные как ошибки компиляции, поэтому, если вы не собираетесь проверять наличие ошибок, вы должны назначить их пустому идентификатору. Но как бы удобно это ни было, ошибки не следует игнорировать.


( 4 // игнорирование ошибок небезопасно, и вы не должны полагаться на результат прежде, чем проверите наличие ошибок)
результату нельзя доверять до проверки на наличие ошибок

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


Пустой идентификатор темен и полон ужасов.

У Go действительно есть panic и recover механизмы, которые также описаны в другом подробном посте в блоге Go. Но они не предназначены для имитации исключений. По словам Дейва, «Когда вы паникуете в Go — вы действительно паникуете: это не проблема кого-то другого, это уже геймовер». Они фатальны и приводят к сбою в вашей программе. Роб Пайк придумал поговорку «Не паникуйте», которая говорит сама за себя: вам, вероятно, следует избегать эти механизмы и вместо них возвращать ошибки.

«Ошибки — значения».
«Не просто проверяйте наличие ошибок, а элегантно их обрабатывайте»
«Не паникуйте»
все поговорки Роба Пайка

Под капотом

Интерфейс ошибки

Под капотом тип error — это простой интерфейс с одним методом, и если вы с ним не знакомы, я настоятельно рекомендую просмотреть этот пост в официальном блоге Go.


интерфейс error из исходного кода

Свои собственные ошибки реализовать не сложно. Существуют различные подходы к пользовательским структурам, реализующим метод Error() string . Любая структура, реализующая этот единственный метод, считается допустимым значением ошибки и может быть возвращена как таковая.

Давайте рассмотрим несколько таких подходов.

Встроенная структура errorString

Наиболее часто используемая и широко распространенная реализация интерфейса ошибок — это встроенная структура errorString . Это самая простая реализация, о которой вы только можете подумать.


Источник: исходный код Go

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


(13// вывод:)

попробуйте

github.com/pkg/errors

Другой простой пример — пакет pkg/errors. Не путать со встроенным пакетом errors, о котором вы узнали ранее, этот пакет предоставляет дополнительные важные возможности, такие как обертка ошибок, развертка, форматирование и запись стек-трейса. Вы можете установить пакет, запустив go get github.com/pkg/errors.

В тех случаях, когда вам нужно прикрепить стек-трейс или необходимую информацию об отладке к вашим ошибкам, использование функций New или Errorf этого пакета предоставляет ошибки, которые уже записываются в ваш стек-трейс, и вы так же можете прикрепить простые метаданные, используя его возможности форматирования. Errorf реализует интерфейс fmt.Formatter, то есть вы можете отформатировать его, используя руны пакета fmt ( %s, %v, %+v и т. д.).


(//6 или альтернатива)

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

Обертки ошибок другими ошибками поддерживают Cause() error метод, который возвращает их внутреннюю ошибку. Кроме того, они могут использоваться сerrors.Cause(err error) error функцией, которая извлекает основную внутреннюю ошибку в оборачивающей ошибке.

Работа с ошибками

Утверждение типа

Утверждения типа (Type Assertion) играют важную роль при работе с ошибками. Вы будете использовать их для извлечения информации из интерфейсного значения, а поскольку обработка ошибок связана с пользовательскими реализациями интерфейса error, реализация утверждений на ошибках является очень удобным инструментом.

Его синтаксис одинаков для всех его целей —  x.(T), если  x имеет тип интерфейса.  x.(T) утверждает, что  x не равен nil и что значение, хранящееся в x, относится к типу T. В следующих нескольких разделах мы рассмотрим два способа использования утверждений типа — с конкретным типом T и с интерфейсом типа T.


(2//сокращенный синтаксис, пропускающий логическую переменную ok
3//паника: преобразование интерфейса: интерфейс {} равен nil, а не string
6//удлиненный синтаксис с логической переменной ok
8//не паникует, вместо этого присваивает ok false, когда утверждение ложно
9// теперь мы можем безопасно использовать s как строку)

песочница: panic при укороченном синтаксисе, безопасный удлинённый синтаксис

Дополнительное примечание, касающееся синтаксиса: утверждение типа может использоваться как с укороченным синтаксисом (который паникует при неудачном утверждении), так и с удлиненным синтаксисом (который использует логическое значение OK для указания успеха или неудачи). Я всегда рекомендую брать удлиненный вместо укороченного, так как я предпочитаю проверять переменную OK, а не разбираться с паникой.

Утверждение с интерфейсом типа T

Выполнение утверждения типа x.(T) с интерфейсом типа T подтверждает, что x реализует интерфейс T. Таким образом, вы можете гарантировать, что интерфейсное значение реализует интерфейс, и только если это так, вы сможете использовать его методы.


(5…// утверждаем, что x реализует интерфейс resolver
6…// здесь мы уже можем безопасно использовать этот метод)

Чтобы понять, как это можно использовать, давайте снова взглянем на pkg/errors. Вы уже знаете этот пакет ошибок, так что давайте углубимся в errors.Cause(err error) error функцию.

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


источник: pkg/errors

Функция получает значение ошибки, и она не может предполагать, что получаемый ею err аргумент является ошибкой-оберткой (поддерживаемой Cause методом). Поэтому перед вызовом метода Cause необходимо убедиться, что вы имеете дело с ошибкой, которая реализует этот метод. Выполняя утверждение типа в каждой итерации цикла for, вы можете убедиться, что cause переменная поддерживает метод Cause, и может продолжать извлекать из него внутренние ошибки до тех пор, пока не найдете ошибку, у которой нет Cause.

Создавая простой локальный интерфейс, содержащий только те методы, которые вам нужны, и применяя на нем утверждение, ваш код отделен от других зависимостей. Полученный вами аргумент не обязательно должен быть известной структурой, он просто должен быть ошибкой. Любой тип, реализующий методы Error и Cause, подойдет. Таким образом, если вы реализуете Cause метод в своем типе ошибки, вы можете использовать эту функцию с ним без замедлений.

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

Наконец, если вам нужен только один метод, иногда удобнее сделать утверждение на анонимном интерфейсе, содержащем только метод, на который вы полагаетесь, т. е. v, ok := x.(interface{ F() (int, error) }). Использование анонимных интерфейсов может помочь отделить ваш код от возможных зависимостей и защитить его от возможных изменений в интерфейсах.

Утверждение с конкретным типом T и Type Switch

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

Первый — это второй вариант утверждения типа: выполняется утверждение типа x.(T) с конкретным типом T. Он утверждает, что значение x имеет тип T, или оно может быть преобразовано в тип T.


(2//мы можем использовать v как mypkg.SomeErrorType)

Другой — это шаблон Type Switch. Type Switch объединяют оператор switch с утверждением типа, используя зарезервированное ключевое слово type. Они особенно распространены в обработке ошибок, где знание основного типа переменной ошибки может быть очень полезным.


(3// обработка…
5// обработка…)

Большим недостатком обоих подходов является то, что оба они приводят к связыванию кода со своими зависимостями. Оба примера должны быть знакомы со структурой SomeErrorType (которая, очевидно, должна быть экспортирована) и должны импортировать пакет mypkg.
В обоих подходах при обработке ваших ошибок вы должны быть знакомы с типом и импортировать его пакет. Ситуация усугубляется, когда вы имеете дело с ошибками в обертках, где причиной ошибки может быть ошибка, возникшая из-за внутренней зависимости, о которой вы не знаете и не должны знать.


(7// обработка…
9// обработка…)

Type Switch различают *MyStruct и MyStruct. Поэтому, если вы не уверены, имеете ли вы дело с указателем или фактическим экземпляром структуры, вам придется предоставить оба варианта. Более того, как и в случае с обычными switch, кейсы в Type Switch не проваливаются, но в отличие от обычных Type Switch, использование fallthrough запрещено в Type Switch, поэтому вам придется использовать запятую и предоставлять обе опции, что легко забыть.

Подытожим

Вот и все! Теперь вы знакомы с ошибками и должны быть готовы к устранению любых ошибок, которые ваше приложение Go может выбросить (или фактически вернуть) на ваш путь!
Оба пакета errors представляют простые, но важные подходы к ошибкам в Go, и, если они удовлетворяют вашим потребностям, они являются отличным выбором. Вы можете легко реализовать свои собственные структуры ошибок и пользоваться преимуществами обработки ошибок Go, комбинируя их с pkg/errors.

Когда вы масштабируете простые ошибки, правильное использование утверждений типа может быть отличным инструментом для обработки различных ошибок. Либо с помощью Type Switch, либо путем утверждения поведения ошибки и проверки интерфейсов, которые она реализует.

Что дальше?

Обработка ошибок в Go сейчас очень актуальна. Теперь, когда вы получили основы, вам может быть интересно, что ждет нас в будущем для обработки ошибок Go!

В следующей версии Go 2 этому уделяется много внимания, и вы уже можете взглянуть на черновой вариант. Кроме того, во время dotGo 2019 Марсель ван Лохуизен провел отличную беседу на тему, которую я просто не могу не рекомендовать — «Значения ошибок GO 2 уже сегодня».

Очевидно, есть еще множество подходов, советов и хитростей, и я не могу включить их все в один пост! Несмотря на это, я надеюсь, что вам он понравился, и я увижу вас в следующем выпуске серии «Перед тем как приступать к Go»!

А теперь традиционно ждем ваши комментарии.

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

Примечание. Этот раздел является частью руководства, состоящего из нескольких частей, который начинается с создания модуля Go.

В greetings/greetings.go добавьте код, выделенный ниже.

Нет смысла отправлять приветствие в ответ, если вы не знаете, кого приветствовать. Вернуть вызывающему абоненту сообщение об ошибке, если имя пусто. Скопируйте следующий код в greetings.go и сохраните файл.

package greetings

import (
    "errors"
    "fmt"
)

// Hello возвращает приветствие для указанного человека.
func Hello(name string) (string, error) {
    // Если имя не было указано, возвращаем ошибку с сообщением.
    if name == "" {
        return "", errors.New("empty name")
    }

    // Если имя было получено, возвращаем значение,
    // включающее имя в приветственном сообщении.
    message := fmt.Sprintf("Hi, %v. Welcome!", name)
    return message, nil
}

В этом коде вы:

  • Измените функцию так, чтобы она возвращала два значения: строку и ошибку. Ваш абонент проверит второе значение, чтобы увидеть, произошла ли ошибка. (Любая функция Go может возвращать несколько значений.)
  • Импортируете пакет errors стандартной библиотеки Go, чтобы вы могли использовать его errors.New функцию.
  • Добавляете оператор if, чтобы проверить недействительный запрос (пустую строку, где должно быть имя) и вернуть ошибку, если запрос недействителен. Функция errors.New возвращает ошибку с вашим сообщением внутри.
  • Добавляете nil (что означает отсутствие ошибки) в качестве второго значения в успешном возврате. Таким образом, вызывающий может увидеть, что функция выполнена успешно.

В файле hello/hello.go обработайте ошибку, возвращаемую функцией Hello, вместе со значением, не связанным с ошибкой.
Вставьте следующий код в hello.go.

package main

import (
    "fmt"
    "log"

    "example.com/greetings"
)

func main() {
    // Устанавливаем свойства предопределенного Logger, включая
    // префикс записи журнала и флаг отключения печати
    // время, исходный файл и номер строки.
    log.SetPrefix("greetings: ")
    log.SetFlags(0)

    // Запрос приветственного сообщения.
    message, err := greetings.Hello("")
    // Если вернулась ошибка, выводим ее в консоль и
    // выходим из программы.
    if err != nil {
        log.Fatal(err)
    }

    // Если ошибок не было, распечатываем возвращенное сообщение
    // в консоль.
    fmt.Println(message)
}

В этом коде вы:

  • Настроете пакет log для печати имени команды («greetings: «) в начале сообщений журнала без отметки времени или информации об исходном файле.
  • Присвоете переменным оба возвращаемых значения Hello, включая ошибку.
  • Измените аргумент Hello с имени Gladys на пустую строку, чтобы вы могли опробовать свой код обработки ошибок.
  • Найдете значение ошибки, отличное от нуля. В этом случае нет смысла продолжать.
  • Используете функции из пакета log стандартной библиотеки для вывода информации об ошибках. Если вы получили сообщение об ошибке, вы используете функцию Fatal пакета log, чтобы распечатать ошибку и остановить программу.

В командной строке в каталоге hello запустите hello.go, чтобы убедиться, что код работает.

Теперь, когда вы передаете пустое имя, вы получите сообщение об ошибке.

$ go run .
greetings: empty name
exit status 1

Это обычная обработка ошибок в Go: возвращать ошибку как значение, чтобы вызывающий мог ее проверить.

Далее, в следующем посте, вы будете использовать срез Go, чтобы вернуть случайно выбранное приветствие.


Читайте также:

  • Создание модуля в Golang
  • Создание модуля в Golang: вызов своего кода из другого модуля
  • Создание модуля в Golang: возврат случайного приветствия
  • Создание модуля в Golang: ответные приветствия для нескольких человек
  • Создание модуля в Golang: добавить тест
  • Создание модуля в Golang: скомпилируйте и установите приложение
  • Управление зависимостями в Golang

Канал в Телеграм

Рассказываем и показываем на практических примерах, как обрабатывать различные ошибки в языке программирования Go.

Механизм обработки ошибок в Go отличается от обработки исключений в большинстве языков программирования, ведь в Golang ошибки исключениями не являются. Если говорить в целом, то ошибка в Go — это возвращаемое значение с типомerror, которое демонстрирует сбой. А с точки зрения кода — интерфейс. В качестве ошибки может выступать любой объект, который этому интерфейсу удовлетворяет.

Выглядит это так:

			type error interface {  
    Error() string
}
		

В данной статье мы рассмотрим наиболее популярные способы работы с ошибками в Golang.

  1. Как обрабатывать ошибки в Go?
  2. Создание ошибок
  3. Оборачивание ошибок
  4. Проверка типов с Is и As
  5. Сторонние пакеты по работе с ошибками в Go
  6. Defer, panic and recover
  7. После изложенного

Как обрабатывать ошибки в Go?

Чтобы обработать ошибку в Golang, необходимо сперва вернуть из функции переменную с объявленным типом error и проверить её на nil:

			if err != nil {
	return err
}
		

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

Если метод возвращает разные типы ошибок, то их нужно проверять отдельно. То есть сначала происходит определение ошибки, а потом для каждого типа пишется свой обработчик.

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

Создание ошибок

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

  • errors.New
  • fmt.Errorf

Метод errors.New() создаёт ошибку, принимая в качестве параметра текстовое сообщение.

			package main

import (
	"errors"
	"fmt"
)

func main() {
	err := errors.New("emit macho dwarf: elf header corrupted")
	fmt.Print(err)
}
		

С помощью метода fmt.Errorf можно добавить дополнительную информацию об ошибке. Данные будут храниться внутри одной конкретной строки.

			package main

import (
	"fmt"
)

func main() {
	const name, id = "bueller", 17
	err := fmt.Errorf("user %q (id %d) not found", name, id)
	fmt.Print(err)
}
		

Такой способ подходит, если эта дополнительная информация нужна только для логирования на вызывающей стороне. Если же с ней предстоит работать, можно воспользоваться другими механизмами.

Оборачивание ошибок

Поскольку Error — это интерфейс, можно создать удовлетворяющую ему структуру с собственными полями. Тогда на вызывающей стороне этими самыми полями можно будет оперировать.

			package main

import (
  "fmt"
)

type NotFoundError struct {
  UserId int
}

func (err NotFoundError) Error() string {
  return fmt.Sprintf("user with id %d not found", err.UserId)
}

func SearchUser(id int) error {
  // some logic for search
  // ...
  // if not found
  var err NotFoundError
  err.UserId = id
  return err
}

func main() {
  const id = 17
  err := SearchUser(id)
  if err != nil {
     fmt.Println(err)
     //type error checking
     notFoundErr, ok := err.(NotFoundError)
     if ok {
        fmt.Println(notFoundErr.UserId)
     }
  }
}
		

Представим другую ситуацию. У нас есть метод, который вызывает внутри себя ещё один метод. В каждом из них проверяется своя ошибка. Иногда требуется в метод верхнего уровня передать сразу обе эти ошибки.

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

Также для оборачивания ошибок в fmt.Errorf есть плейсхолдер %w, который и позволяет произвести такую упаковку.:

			package main

import (
	"errors"
	"fmt"
	"os"
)

func main() {
	err := openFile("non-existing")
	if err != nil {
		fmt.Println(err.Error())
		// get internal error
		fmt.Println(errors.Unwrap(err))
	}
}

func openFile(filename string) error {
	if _, err := os.Open(filename); err != nil {
		return fmt.Errorf("error opening %s: %w", filename, err)
	}
	return nil
}
		

Проверка типов с Is и As

В Go 1.13 в пакете Errors появились две функции, которые позволяют определить тип ошибки — чтобы написать тот или иной обработчик:

  • errors.Is
  • errors.As

Метод errors.Is, по сути, сравнивает текущую ошибку с заранее заданным значением ошибки:

			package main

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
)

func main() {
	if _, err := os.Open("non-existing"); err != nil {
		if errors.Is(err, fs.ErrNotExist) {
			fmt.Println("file does not exist")
		} else {
			fmt.Println(err)
		}
	}
}
		

Если это будет та же самая ошибка, то функция вернёт true, если нет — false.

errors.As проверяет, относится ли ошибка к конкретному типу (раньше надо было явно приводить тип ошибки к тому типу, который хотим проверить):

			package main

	import (
	"errors"
	"fmt"
	"io/fs"
	"os"
)

func main() {
	if _, err := os.Open("non-existing"); err != nil {
		var pathError *fs.PathError
		if errors.As(err, &pathError) {
			fmt.Println("Failed at path:", pathError.Path)
		} else {
			fmt.Println(err)
		}
	}
}
		

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

Сторонние пакеты по работе с ошибками в Go

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

В качестве примера можно посмотреть на пакет pkg/errors. Одной из его способностей является логирование stack trace:

			package main

import (
	"fmt"
	"github.com/pkg/errors"
)

func main() {
	err := errors.Errorf("whoops: %s", "foo")
	fmt.Printf("%+v", err)
}
	// Example output:
	// whoops: foo
	// github.com/pkg/errors_test.ExampleErrorf
	//         /home/dfc/src/github.com/pkg/errors/example_test.go:101
	// testing.runExample
	//         /home/dfc/go/src/testing/example.go:114
	// testing.RunExamples
	//         /home/dfc/go/src/testing/example.go:38
	// testing.(*M).Run
	//         /home/dfc/go/src/testing/testing.go:744
	// main.main
	//         /github.com/pkg/errors/_test/_testmain.go:102
	// runtime.main
	//         /home/dfc/go/src/runtime/proc.go:183
	// runtime.goexit
	//         /home/dfc/go/src/runtime/asm_amd64.s:2059
		

Defer, panic and recover

Помимо ошибок, о которых позаботился разработчик, в Go существуют аварии (похожи на исключительные ситуации, например, в Java). По сути, это те ошибки, которые разработчик не предусмотрел.

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

Для работы с такими ошибками существует механизм «defer, panic, recover»

Defer

Defer помещает все вызовы функции в стек приложения. При этом отложенные функции выполняются в обратном порядке — независимо от того, вызвана паника или нет. Это бывает полезно при очистке ресурсов:

			package main

import (
    "fmt"
    "os"
)

func main() {
    f := createFile("/tmp/defer.txt")
    defer closeFile(f)
    writeFile(f)
}

func createFile(p string) *os.File {
    fmt.Println("creating")
    f, err := os.Create(p)
    if err != nil {
        panic(err)
    }
    return f
}

func writeFile(f *os.File) {
    fmt.Println("writing")
    fmt.Fprintln(f, "data")
}

func closeFile(f *os.File) {
    fmt.Println("closing")
    err := f.Close()
    if err != nil {
        fmt.Fprintf(os.Stderr, "error: %v\n", err)
        os.Exit(1)
    }
}
		

Panic

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

Например, Golang будет «паниковать», когда число делится на ноль:

			panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.divide(0x0)
        C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:16 +0xe6
main.divide(0x1)
        C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:17 +0xd6
main.divide(0x2)
        C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:17 +0xd6
main.divide(0x3)
        C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:17 +0xd6
main.divide(0x4)
        C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:17 +0xd6
main.divide(0x5)
        C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:17 +0xd6
main.main()
        C:/Users/gabriel/articles/Golang Error handling/Code/panic/main.go:11 +0x31
exit status 2
		

Также панику можно вызвать явно с помощью метода panic(). Обычно его используют на этапе разработки и тестирования кода — а в конечном варианте убирают.

Recover

Эта функция нужна, чтобы вернуть контроль при панике. В таком случае работа приложения не прекращается, а восстанавливается и продолжается в нормальном режиме.

Recover всегда должна вызываться в функции defer. ​​Чтобы сообщить об ошибке как возвращаемом значении, вы должны вызвать функцию recover в той же горутине, что и паника, получить структуру ошибки из функции восстановления и передать её в переменную:

			package main

import (
	"errors"
	"fmt"
)

func A() {
	defer fmt.Println("Then we can't save the earth!")
	defer func() {
		if x := recover(); x != nil {
			fmt.Printf("Panic: %+v\n", x)
		}
	}()
	B()
}

func B() {
	defer fmt.Println("And if it keeps getting hotter...")
	C()
}

func C() {
	defer fmt.Println("Turn on the air conditioner...")
	Break()
}

func Break() {
	defer fmt.Println("If it's more than 30 degrees...")
	panic(errors.New("Global Warming!!!"))
}

func main() {
	A()
}
		

После изложенного

Можно ли игнорировать ошибки? В теории — да. Но делать это нежелательно. Во-первых, наличие ошибки позволяет узнать, успешно ли выполнился метод. Во-вторых, если метод возвращает полезное значение и ошибку, то, не проверив её, нельзя утверждать, что полезное значение корректно.

Надеемся, приведённые методы обработки ошибок в Go будут вам полезны. Читайте также статью о 5 главных ошибках Junior-разработчика, чтобы не допускать их в начале своего карьерного пути.

In this article, we shall be discussing how to return and handle errors effectively using custom and inbuilt Golang functions, with help of practical examples.

Golang return error

An error is basically a fault that occurs in a program execution flow. These errors can be of various natures:- Caused by programmers through code syntax and interface errors , system-related Resources and Runtime errors, algorithm-related logic and arithmetic errors. Which later are solved through debugging process.

In Golang ,The Error is an interface that holds Error() string method. Its implemented as follows

type error interface {
Error() string
}

In an nutshell, when the Error() method is called, it’s return value is in form of string datatype. Through the use of inbuilt Go functions of the fmt and errors packages, we can construct the kind of error message to be displayed. Below is an example to construct Errors using fmt.Error() in Golang, i.e you want to read a file from a given path, unfortunate the file doesn’t exist or the path given is invalid. For example:=

package main

import (
	"fmt"
	"os"
)

func ReadFile(file string) error {
	dataFile, err := os.ReadFile(file)
	if err != nil {
		return fmt.Errorf("An error occurred while Reading the file: open : %v", err)
	}
	fmt.Println(string(dataFile))
	return nil
}
func main() {
	resultsErr := ReadFile("")
	if resultsErr != nil {
		fmt.Printf("%v", resultsErr)
	}
}

Output:

With the file attached ensure you replace the ReadFile(«test.txt»)

$ go run main.go
Hello

without file attached

$ go run main.go
An error occurred while Reading the file: open: no such file or directory

Explanation:- In the above code, ReadFile() error{} function returns an error which is nil whenever no error encountered. In Golang, the Error return value is nil as the default, or “zero”. Notice that checking if err != nil{} is the idiomatic way to determine if an error was encountered in Golang syntax, in this function we are returning the error only, handling the file data within the function. fmt.Error() enables us to customize the kind of message to be displayed. These messages are always in a lowercase format and don’t end with punctuation.

In Golang there are numerous ways to return and handle errors Namely:=

  • Casting Errors
  • Error wrapping mechanism
  • Panic, defer and recover

ALSO READ: How to PROPERLY uninstall Golang? [Step-by-Step]

Different methods of error handling in Go Func

Method 1:- Casting Errors

Casting errors is a way of defining custom and expected errors, with golang we can make use of erros.Isand errors.As() error functions to cast different types of errors. i.e,errors.Is we create a custom error of a particular type and check If the error matches the specific type the function will return true, if not it will return false.

package main

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
)

var fileNotFound = errors.New("The file doesn't  exist")

func ReadFile(file string) error {
	dataFile, readErr := os.ReadFile(file)
	if readErr != nil {
		if errors.Is(readErr, fs.ErrNotExist) {
			return fmt.Errorf("this fileName %s  doesn't exist ", file)
		} else {
			return fmt.Errorf("Error  occured while opening the file : %w", readErr)
		}
	}
	fmt.Println(string(dataFile))
	return nil
}
func main() {
	fileName := os.Args[1]
	if fileName != "" {
		resultsError := ReadFile(fileName)
		if resultsError != nil {
			fmt.Printf("%v", resultsError)
		}
	} else {
		fmt.Println("the file name cant be empty")
	}
}

Output:

$ go run main.go "new"
this fileName new  doesn't exist

Explanation:- We are using errors.Is(readErr, fs.ErrNotExist) {} to check if the file passed exist, if it doesn’t exist we return custom message as shown above. we can also use the custom error message such as errors.New() to create expected error and handle it as errors.Is(readErr, fileNotFound) {} the return values will be the same.

Method 2:- Error wrapping

Wrapping is a way of using other errors within a function to provide more context and detailed error messages.

fmt.Error() function enable us to create a wrapped errors with use of %w flag. The %w flag is used for inspecting and unwrapping errors.
In this subtitles we can incorporate other functions from errors package used to handle errors, namely:- errors.As, errors.Is, errors.Unwrap functions. errors.As is used to cast a specific error type, i.e func As(err error, target any) bool{}, also, the errors.Unwrap is used to inspect and expose the underlying errors in a program,i.e func (e *PathError)Unwrap()error{ return e.Err}, Furthermore the errors.Is mostly for comparing error value against the sentinel value if is true or false, i.e func Is(err,target error) bool{}.

Example of Error Wrapping

package main

import (
	"errors"
	"fmt"
	"os"
)

func ReadFile(file string) error {
	dataFile, readErr := os.ReadFile(file)
	var pathError *os.PathError
	if readErr != nil {
		if errors.As(readErr, &pathError) {
			return fmt.Errorf("this fileName %s  doesn't exist and failed  opening file at this path %v", file, pathError.Path)
		}
		return fmt.Errorf("Error  occured while opening the file : %w", readErr)

	}
	fmt.Println(string(dataFile))
	return nil
}
func main() {
	fileName := os.Args[1]
	if fileName != "" {
		resultsError := ReadFile(fileName)
		if resultsError != nil {
			fmt.Printf("%v", resultsError)
		}
	} else {
		fmt.Println("the file name can't be empty")
	}
}

Output:

ALSO READ: Golang Bool to String Type Casting [2 Methods]

With the file attached ensure you replace the ReadFile(«test.txt»)

$ go run main.go
Hello

without file attached

$ go run main.go ""
the file name can't be empty
$ go run main.go next.txt
this fileName news.txt  doesn't exist and failed opening file at this path news.txt

Explanation:- In the above code we have used fmt.Errorf() functions to format the error message to be displayed and wrapping error using a custom error message with wrap function errors.Is() which checks if the path exists. You can avoid unnecessary error wrapping and handle it once.

Method-3: Using Panic, Defer and Recover

We have covered this topic in detail in a separate article Golang panic handing [capture, defer, recover, log]

Summary

At this point in this article, you have learned various ways to return and handle errors in the Golang function. In Go, Errors are considered to be a very lightweight piece of data that implements the Error interface. Custom errors in Go help in debugging and signaling where the error has occurred from. Error tracing is easy as compared to other programming languages. Golang application development, error handling is very critical and helps one not only during debugging but also to monitor the application behavior. We recommend you to read more about panic, recover and defer mechanism of error handling as well.

References

error-handling in Go
Working with errors in golang
Errors

Понравилась статья? Поделить с друзьями:
  • Gog ошибка e15
  • God of war не запускается ошибка
  • God of war на пк ошибка d3d
  • Gog galaxy ошибка при запуске приложения 0xc000007b
  • God of war выдает ошибку при запуске