Александр Тихоненко
Ведущий разработчик трайба «Автоматизация бизнес-процессов» МТС Диджитал
Механизм обработки ошибок в Go отличается от обработки исключений в большинстве языков программирования, ведь в Golang ошибки исключениями не являются. Если говорить в целом, то ошибка в Go — это возвращаемое значение с типомerror
, которое демонстрирует сбой. А с точки зрения кода — интерфейс. В качестве ошибки может выступать любой объект, который этому интерфейсу удовлетворяет.
Выглядит это так:
type error interface {
Error() string
}
В данной статье мы рассмотрим наиболее популярные способы работы с ошибками в Golang.
- Как обрабатывать ошибки в Go?
- Создание ошибок
- Оборачивание ошибок
- Проверка типов с Is и As
- Сторонние пакеты по работе с ошибками в Go
- Defer, panic and recover
- После изложенного
Чтобы обработать ошибку в 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: %vn", 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: %+vn", 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-разработчика, чтобы не допускать их в начале своего карьерного пути.
2 сентября, 2019 12:13 пп
1 272 views
| Комментариев нет
Cloud Server
Устойчивый и надежный код должен уметь правильно реагировать на неожиданные обстоятельства: например, на неправильный пользовательский ввод, сетевые сбои и ошибки диска. Обработка ошибок – это процесс обнаружения непредсказуемых состояний и записи диагностической информации для дальнейшего устранения неполадок.
В отличие от других языков, где ошибки обрабатываются разработчиками с помощью специального синтаксиса, ошибки в Go – это значения, которые относятся к типу данных error. Они используются функциями, как и остальные типы данных. Чтобы обработать ошибки в Go, нужно изучить ошибки, которые могут возвращать функции, решить, произошла ли ошибка, принять надлежащие меры для защиты данных и сообщить пользователям или операторам, что произошла ошибка.
Создание ошибок
Прежде чем мы сможем обрабатывать ошибки, нужно сначала их создать. В Go есть стандартная библиотека, которая предоставляет две встроенные функции для создания ошибок: errors.New и fmt.Errorf. Обе эти функции позволяют указывать пользовательское сообщение об ошибке, которое вы можете позже представить своим пользователям.
Функция errors.New принимает один аргумент – сообщение об ошибке в виде строки, оно позволяет вам предупредить своих пользователей о том, что пошло не так.
Попробуйте запустить следующий пример кода, чтобы увидеть в стандартном выводе ошибку, созданную errors.New:
package main
import (
"errors"
"fmt"
)
func main() {
err := errors.New("barnacles")
fmt.Println("An error occurred:", err)
}
An error occurred: barnacles
Мы использовали функцию errors.New из стандартной библиотеки, чтобы создать новое сообщение об ошибке со строкой “barnacles” в качестве сообщения об ошибке. При этом мы следовали соглашению, используя строчные буквы, как предлагает руководство по стилю Go.
Затем мы использовали функцию fmt.Println, чтобы объединить наше сообщение об ошибке со строкой «An error occurred:».
Функция fmt.Errorf позволяет динамически создавать сообщения об ошибке. Ее первым аргументом является строка, содержащая ваше сообщение об ошибке со значениями-заполнителями: %s для строки и %d для целого числа. fmt.Errorf интерполирует аргументы, следующие за этой строкой, в указанные заполнители в таком порядке:
package main
import (
"fmt"
"time"
)
func main() {
err := fmt.Errorf("error occurred at: %v", time.Now())
fmt.Println("An error happened:", err)
}
An error happened: Error occurred at: 2019-07-11 16:52:42.532621 -0400 EDT m=+0.000137103
Мы использовали функцию fmt.Errorf для создания сообщения об ошибке, которое будет указывать текущее время. Строка форматирования, которую мы предоставили fmt.Errorf, содержит директиву %v, которая задает форматирование по умолчанию для первого аргумента после строки. Этим аргументом будет текущее время, предоставленное функцией time.Now из стандартной библиотеки. Как и в предыдущем примере, мы объединяем сообщение об ошибке с коротким префиксом и выводим результат в стандартный вывод, используя функцию fmt.Println.
Обработка ошибок
Как правило, ошибки, созданные так, как в предыдущем примере – без причины – не используются. На практике гораздо чаще ошибка создается и возвращается из функции, когда что-то идет не так. Вызывающие эту функцию затем используют оператор if, чтобы узнать, была создана ошибка или ноль – неинициализированное значение.
Следующий пример включает функцию, которая всегда возвращает ошибку. Обратите внимание, что при запуске программы она выдает тот же результат, что и в предыдущем примере, хотя функция на этот раз возвращает ошибку. Объявление ошибки в другом месте не изменяет сообщения об ошибке.
package main
import (
"errors"
"fmt"
)
func boom() error {
return errors.New("barnacles")
}
func main() {
err := boom()
if err != nil {
fmt.Println("An error occurred:", err)
return
}
fmt.Println("Anchors away!")
}
An error occurred: barnacles
Здесь мы определили функцию boom(), которая возвращает единичную ошибку, созданную с помощью error.New. Затем мы вызвали эту функцию и зафиксировали ошибку с помощью строки err := boom().
Как только мы присваиваем эту ошибку, мы проверяем, присутствовала ли она с условием if err != nil . Здесь условное выражение всегда будет иметь значение true, поскольку мы всегда возвращаем error из boom().
Но это не всегда так работает, поэтому рекомендуем использовать логику для обработки случаев, когда ошибка отсутствует (nil), и случаев, когда ошибка присутствует. Когда ошибка есть, мы используем fmt.Println, чтобы вывести ее вместе с префиксом, как в предыдущих примерах. В конце оператор return позволяет пропустить выполнение fmt.Println(“Anchors away!”) – эта функция должна выполняться только тогда, когда ошибок нет.
Примечание: Конструкция if err != nil , показанная в последнем примере, является основным компонентом обработки ошибок на Go. Везде, где функция может вызвать ошибку, важно использовать оператор if, чтобы проверить, произошла ли она. Таким образом идиоматический код Go поддерживает логику “happy path” на первом уровне отступа и логику “sad path” на втором уровне.
Операторы if имеют опциональный оператор присваивания, который можно использовать для упрощения вызова функции и обработки ошибок.
Запустите следующую программу, и вы получите тот же вывод, что и в предыдущем примере. Но на этот раз мы используем составной оператор if, чтобы сократить шаблон:
package main
import (
"errors"
"fmt"
)
func boom() error {
return errors.New("barnacles")
}
func main() {
if err := boom(); err != nil {
fmt.Println("An error occurred:", err)
return
}
fmt.Println("Anchors away!")
}
An error occurred: barnacles
Как и раньше, у нас есть функция boom(), которая всегда возвращает ошибку. Мы присваиваем ошибку из boom(), в качестве первой части оператора if. Во второй части оператора после точки с запятой переменная err становится доступной. Мы проверяем, присутствует ли ошибка, и выводим ошибку с короткой префиксной строкой, как и раньше.
Возврат ошибок вместе со значениями
Функции, которые возвращают единичную ошибку, часто влияют на некоторые stateful изменения (такие как вставка строк в базу данных). Также часто используются функции, которые возвращают значение в случае успешного выполнения и потенциальную ошибку в обратном случае. Go позволяет функциям возвращать более одного результата, который можно использовать для одновременного возврата значения и ошибки.
Чтобы создать функцию, которая возвращает более одного значения, нужно перечислить все типы возвращаемых значений в скобках в сигнатуре функции. Например, функция capitalize, которая возвращает string и error, будет объявлена с помощью func capitalize(name string) (string, error) {}. Часть (string, error) сообщает компилятору Go, что эта функция выводит string и error в указанном порядке.
Запустите эту программу, чтобы получит вывод функции, которая может выводить string и error.
package main
import (
"errors"
"fmt"
"strings"
)
func capitalize(name string) (string, error) {
if name == "" {
return "", errors.New("no name provided")
}
return strings.ToTitle(name), nil
}
func main() {
name, err := capitalize("myname")
if err != nil {
fmt.Println("Could not capitalize:", err)
return
}
fmt.Println("Capitalized name:", name)
}
Capitalized name: MYNAME
Мы определяем capitalize() как функцию, которая принимает строку (имя, которое нужно записать заглавными буквами) и возвращает значение строки и ошибки. В main() мы вызываем capitalize() и присваиваем переменным name и err два значения, возвращаемых функцией, разделяя их запятыми в левой части оператора :=. После этого мы выполняем проверку if err != nil , как в предыдущих примерах, и выводим ошибку в стандартный вывод, используя fmt.Println, если ошибка есть. Если ошибки не было, Программа выведет Capitalized name: MYNAME.
Попробуйте заменить строку “myname” в name, err := capitalize(“myname”) на пустую строку (“”), и вы получите сообщение об ошибке.
Could not capitalize: no name provided
Функция capitalize вернет ошибку, если вызывающие функции задают для параметра name пустую строку. Когда параметр name не является пустой строкой, capitalize() использует strings.ToTitle, чтобы записать заглавными буквами параметр name, и возвращает nil в качестве значения ошибки.
Есть несколько соглашений, которым следует этот пример. Они типичны для кода Go, но не соблюдаются компилятором. Когда функция возвращает несколько значений, включая ошибку, соглашение требует, чтобы мы вернули error как последний элемент. При возврате error из функции с несколькими значениями идиоматический код Go также установит нулевое значение для каждого значения, не являющегося ошибкой. Нулевыми значениями являются, например, пустая строка для строчных типов, 0 для целых чисел, пустая структура для структурных типов и nil для интерфейсов.
Читайте также: Переменные и константы в Go
Уменьшение шаблона
Соблюдать эти соглашения сложно в ситуациях, когда из функции нужно вернуть много значений. Мы можем использовать анонимную функцию, чтобы уменьшить шаблон. Анонимные функции – это процедуры, присваиваемые переменным. В отличие от функций, которые мы определили в предыдущих примерах, они доступны только в функциях, в которых вы их объявляете – потому их очень удобно использовать в качестве коротких фрагментов многократно используемой вспомогательной логики.
Следующая программа модифицирует последний пример, задавая длину имени, которое нужно написать заглавными буквами. Так как пример выводит три значения, без анонимной функции обработка ошибок может стать сложной:
package main
import (
"errors"
"fmt"
"strings"
)
func capitalize(name string) (string, int, error) {
handle := func(err error) (string, int, error) {
return "", 0, err
}
if name == "" {
return handle(errors.New("no name provided"))
}
return strings.ToTitle(name), len(name), nil
}
func main() {
name, size, err := capitalize("myname")
if err != nil {
fmt.Println("An error occurred:", err)
}
fmt.Printf("Capitalized name: %s, length: %d", name, size)
}
Capitalized name: MYNAME, length: 6
В main()теперь собрано три возвращаемых аргумента из capitalize: name, size и err соответственно. Затем мы проверяем, вернула ли capitalize ошибку – смотрим, была ли err равна nil. Это важно сделать, прежде чем пытаться использовать какие-либо другие значения, возвращаемые capitalize, потому что анонимная функция handle может установить для них нулевые значения. Поскольку ошибки не произошло, так как мы указали строку “myname”, мы получаем имя заглавными буквами и его длину.
Тут вы снова можете попробовать изменить “myname” на пустую строку (“”), чтобы увидеть ошибку:
An error occurred: no name provided
В рамках capitalize мы определяем переменную handle как анонимную функцию. Она принимает единичную ошибку и возвращает идентичные значения в том же порядке, что и capitalize. Переменная handle приравнивает эти значения нулю и пересылает ошибку, переданную как аргумент, в качестве окончательного значения. Используя это, мы можем возвращать любые ошибки capitalize, используя оператор return перед вызовом handle с параметром error.
Помните, что capitalize должна все время возвращать три значения, поскольку именно так мы определили эту функцию. Но иногда нужны не все значения, которые может вернуть функция. К счастью, эти значения можно гибко присваивать.
Обработка ошибок из функций множественного возврата
Когда функция возвращает много значений, Go требует, чтобы мы присвоили каждое из них переменной. В последнем примере мы так и сделали. При этом имена должны быть разделены запятыми и отображаться слева от оператора :=. Первое значение, возвращаемое capitalize, будет присвоено переменной name, а второе значение (error) будет присвоено переменной err. Иногда нам нужно только значение ошибки. В таком случае можно отказаться от любых нежелательных значений, возвращаемых функциями, используя специальное имя _.
В следующей программе мы изменили первый пример функции capitalize, передав пустую строку (“”) – теперь он выдаст ошибку. Попробуйте запустить эту программу, чтобы увидеть, как проверить только значение ошибки, отбросив первое возвращаемое значение с помощью переменной _:
package main
import (
"errors"
"fmt"
"strings"
)
func capitalize(name string) (string, error) {
if name == "" {
return "", errors.New("no name provided")
}
return strings.ToTitle(name), nil
}
func main() {
_, err := capitalize("")
if err != nil {
fmt.Println("Could not capitalize:", err)
return
}
fmt.Println("Success!")
}
Could not capitalize: no name provided
В этот раз в функции main() мы присвоили имя capitalized переменной _. Затем мы присвоили error, возвращаемую capitalize, переменной err. После этого мы проверили, присутствовала ли ошибка в условном выражении if err != nil. Поскольку в качестве аргумента для capitalize мы жестко закодировали пустую строку в строке _, err := capitalize(“”), это условие всегда будет иметь значение true. Это приводит к выводу:
Could not capitalize: no name provided
Его возвращает функция fmt.Println в теле оператора if. После этого return пропустит fmt.Println(“Success!”).
Заключение
Теперь вы знаете несколько способов создания ошибок через стандартную библиотеку и знаете, как создавать функции, которые возвращают ошибки идиоматическим способом. Также вы знаете, как работают функции errors.New и fmt.Errorf. В следующих уроках мы рассмотрим, как создавать собственные типы ошибок, чтобы предоставить пользователям более подробную информацию.
Tags: Go, Golang
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:
ALSO READ: GO Import struct from another file [SOLVED]
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
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.
ALSO READ: Getting started with Golang Gopher
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:
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.
ALSO READ: Containerizing Golang App with Docker [Tutorial]
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
Слышатся сирены. Студенты и учителя быстро выскочили из классов и столпились у точки сбора. Никакой опасности в поле зрения нет и ничего не горит. Это очередная учебная пожарная тревога. В случае реальной чрезвычайной ситуации все точно будут знать, что делать.
Премиум 👑 канал по Golang
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Подписаться на канал
Уроки, статьи и Видео
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
Go в ВК
ЧАТ в Telegram
Содержание статьи
- Исправление ошибок в Golang
- Элегантная обработка ошибок
- Запись данных в файле
- Применяем defer — отложенные действия
- Креативная обработка ошибок
- Новые ошибки в программе на Golang
- Причины каждой ошибки в Go
- Настраиваемые типы ошибок
- Множество ошибок в Golang
- Утверждение типа Go
- Принцип работы panic
- Есть ли исключения в Golang?
- Как использовать panic
- Тонкости работы с panic в Go
Файл не найден, неверный формат, сервер недоступен. Что делает программа, когда что-то идет не так? Возможно, проблему можно решить, и тогда операции будут выполняться должным образом. Иногда лучше всего просто выйти и закрыть двери — или на крайний случай разбить окно и выскочить наружу.
План всегда важен. Рассмотрим возможные ошибки и способы их исправления. Go всегда ответственно подходит к вопросу устранения ошибок, побуждая вас задуматься о причинах ошибки, что поможет ее решить. Как и десятая пожарная тренировка, обработка ошибок может показаться монотонной, однако результат того стоит.
В данном уроке будут рассмотрены способы исправления ошибок и выяснения причины их появления. Под конец сравним стиль исправления ошибок Go и в других языках программирования.
В начала 18 века английский поэт Александр Поуп написал поэму, строчка которой известна по сей день: to err is human, то есть человеку свойственно ошибаться. Подумайте, как данную строку можно сравнить с программированием.
To err is human; to forgive, divine.
Александ Поуп, “An Essay on Criticism: Part 2”
Все делают ошибки. Системы не исключение. Ошибки повсеместны. Они не считаются редким явлением, поэтому лучше быть готовым. Принимайте ошибки, не игнорируйте их. Работайте над исправлением и двигайтесь дальше.
Исправление ошибок в Golang
В языках программирования прошлого лимитация на одно возвращаемое значение делали исправление ошибок не совсем понятным. Функции переполняли бы одинаковое возвращаемое значение для указания как ошибки, так и успешного значения, или запрашивали бы побочный канал для обращения к ошибки вроде глобальной переменной errno
. Что еще хуже, механизм сообщения об ошибках было непоследовательным от функции к функции.
У Go есть несколько возвращаемых значений, как упоминалось в уроке о функциях. Хотя это не относится к обработке ошибок, несколько возвращаемых значений обеспечивают простой и последовательный механизм возврата ошибки к вызову функций. Если функция может вернуть ошибку, соглашение состоит в том, чтобы использовать последнее возвращаемое значение для ошибок. Вызывающий элемент должен проверить, произошла ли ошибка сразу после вызова функции. Если ошибок не было, значение ошибки будет равно nil
.
Чтобы продемонстрировать обработку ошибок, Листинг 1 вызывает функцию ReadDir
. Если возникает ошибка, переменная err
не будет равна nil, что заставит программу вывести ошибку и немедленно завершить работу. Ненулевое значение, переданное os.Exit
, сообщает операционной системе, что произошла ошибка.
Если ReadDir
успешно выполнена, files
будет назначен к срезу os.FileInfo
, предоставляющий информацию о файлах и каталогах по указанному пути. В данном случае точка уточняет путь, указывающий текущую директорию.
files, err := ioutil.ReadDir(«.») if err != nil { fmt.Println(err) os.Exit(1) } for _, file := range files { fmt.Println(file.Name()) } |
Когда возникает ошибка, не стоит полагаться на другие возвращаемые значения. Они могут быть нулевыми для своего типа, но некоторые функции возвращают данные частично или же совершенно иные значения.
При запуске Листинга 1 на Go Playground в выводе будет список директорий:
Для создания списка содержимого другой директории замените текущую директорию ("."
) в Листинге 1 названием другой директории вроде "etc"
. Список может содержать как файлы, так и директории. Вы можете использовать file.IsDir()
для того, чтобы различить их.
Вопросы для проверки:
- Переделайте Листинг 1 для чтения воображаемой директории под названием
"unicorns"
. Какая ошибка выйдет? - Какое сообщение об ошибке выйдет при использовании
ReadDir
над файлом"/etc/hosts"
вместо директории.
Элегантная обработка ошибок в Golang
Разработчикам Go рекомендуется учитывать и обрабатывать любые ошибки, которые возвращают функции. Количество кода для обработки ошибок увеличивается довольно быстро. К счастью, есть несколько способов уменьшить размер кода обработки ошибок без ущерба надежности.
Некоторые функции выполняют вычисления, преобразования данных и другую логику, где ошибки будут некстати. Есть функции, которые взаимодействуют с файлами, базами данных и серверами. Связь несколько ненадежна и может потерпеть неудачу. Одной из стратегий уменьшения кода обработки ошибок является изоляция безошибочного подмножества программы от изначально подверженного ошибкам кода.
Но как насчет кода, который возвращает ошибки? Мы не можем удалить ошибки, но можем упростить код обработки ошибок. Чтобы продемонстрировать это, напишем небольшую программу для записи в файл следующих английских слоганов Go (Go Proverbs), а затем улучшим обработку ошибок, пока код не станет приемлемым.
Errors are values.
Don’t just check errors, handle them gracefully.
Don’t panic.
Make the zero value useful.
The bigger the interface, the weaker the abstraction.
interface{} says nothing.
Gofmt’s style is no one’s favorite, yet gofmt is everyone’s favorite.
Documentation is for users.
A little copying is better than a little dependency.
Clear is better than clever.
Concurrency is not parallelism.
Don’t communicate by sharing memory, share memory by communicating.
Channels orchestrate; mutexes serialize.Rob Pike, Go Proverbs
Запись данных в файле
При записи файла множество вещей может пойти не так. Если путь неправильный, или есть проблемы с разрешением, столкнуться с ошибкой можно еще перед началом записи. По окончании записи на диске устройства может закончится место, или же съемный диск может быть случайно извлечен. В дополнении ко всему в конце файл должен быть закрыт, это позволяет убедиться, что все успешно записано на диск, а также избежать утечки информации.
На заметку: Операционная система ограничивает число открытых файлов, поэтому при открытии каждого нового файла память уменьшается. Когда файл не используется, но при этом остается открытым, расход ресурсов является примером утечки.
Главная функция в Листинге 2 вызывает proverbs
для создания файла и обрабатывает любые ошибки, отображая ее и затем выходя. Другая имплементация может обрабатывать ошибки иначе, возможно, подсказывая пользователю другой путь или название файла. Хотя функция proverbs
может быть написана так, чтобы она выходила при возникновении ошибок, полезно разрешить вызывающему элементу решать, как обрабатывать ошибки.
err := proverbs(«proverbs.txt») if err != nil { fmt.Println(err) os.Exit(1) } |
Функция proverbs
может вернуть error
, что является специальным встроенным типом для ошибок. Функция повременит с созданием файла из за ошибки. Если в данный момент возникает ошибка, нет нужды закрывать файл, поэтому процесс обрывается. Оставшаяся часть функции записывает строки в файл и гарантирует, что файл закрыт, независимо от его успешности, как показано в коде ниже.
func proverbs(name string) error { f, err := os.Create(name) if err != nil { return err } _, err = fmt.Fprintln(f, «Errors are values.») if err != nil { f.Close() return err } _, err = fmt.Fprintln(f, «Don’t just check errors, handle them gracefully.») f.Close() return err } |
В предыдущем коде много моментов для обработки ошибок — так много, что запись каждого выражения из «Go Proverbs» может стать довольно утомительной.
Плюсом является тот факт, что у кода для обработки ошибок есть отступ, это позволяет легче ориентироваться в коде. Отступы для ошибок являются общим паттерном Go, что во время имплементации можно усовершенствовать.
Вопрос для проверки:
Почему функции должны возвращать ошибку вместо выхода из программы?
Применяем defer — отложенные действия в Golang
Убедиться, что файл правильно закрыт, можно с помощью ключевого слова defer
. Go гарантирует, что все отложенные действия будут выполнены до возврата содержащей функции. В следующем листинге каждый возвращаемый оператор, следующий за defer
, приведет к вызывающему методу f.Close()
.
func proverbs(name string) error { f, err := os.Create(name) if err != nil { return err } defer f.Close() _, err = fmt.Fprintln(f, «Errors are values.») if err != nil { return err } _, err = fmt.Fprintln(f, «Don’t just check errors, handle them gracefully.») return err } |
На заметку: Поведение предыдущего кода похоже тому, что в Листинге 3. Изменение кода без изменения его поведения называется рефакторингом. Как и переосмысление первого черновика сочинения, рефакторинг является важным навыком для написания лучшего кода.
Вы можете отложить любую функцию или метод, и как множество возвращаемых значений, отсрочка нужна не для уточнения обработки ошибки. Она улучшает обработку ошибок, избавляясь от необходимости постоянно помнить об очистке. Благодаря defer
, код для обработки ошибок может сфокусироваться только на своей задачи и больше ни о чем.
Ключевое слово defer
облегчает процесс, однако проверка на наличие ошибок после каждой строки кода очень утомительна. Пришло время для креативности!
Вопрос для проверки:
Когда будет вызвано отсроченное действие?
Креативная обработка ошибок в Golang
15 января 2015 года в блоге Go была опубликована отличная статья об обработке ошибок. В статье описывается простой способ для записи в файл без повторения одинакового кода для обработки ошибок после каждой строчки.
Для применения данной техники вам понадобится объявить новый тип, что вызывается в Листинге 5. Если при записи safeWriter
в файл возникает ошибка, он сохраняет ошибку вместо ее возвращения. Следующие попытки записи в одинаковый файл будут пропущены, если writeln
видит, что раньше была ошибка.
type safeWriter struct { w io.Writer err error // Место для хранения первой ошибки } func (sw *safeWriter) writeln(s string) { if sw.err != nil { return // Пропускает запись, если раньше была ошибка } _, sw.err = fmt.Fprintln(sw.w, s) // Записывает строку и затем хранить любую ошибку } |
Через использование safeWriter
следующий листинг записывает несколько строк без репетативной обработки ошибок, но по-прежнему возвращает все возникшие ошибки.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
func proverbs(name string) error { f, err := os.Create(name) if err != nil { return err } defer f.Close() sw := safeWriter{w: f} sw.writeln(«Errors are values.») sw.writeln(«Don’t just check errors, handle them gracefully.») sw.writeln(«Don’t panic.») sw.writeln(«Make the zero value useful.») sw.writeln(«The bigger the interface, the weaker the abstraction.») sw.writeln(«interface{} says nothing.») sw.writeln(«Gofmt’s style is no one’s favorite, yet gofmt is everyone’s favorite.») sw.writeln(«Documentation is for users.») sw.writeln(«A little copying is better than a little dependency.») sw.writeln(«Clear is better than clever.») sw.writeln(«Concurrency is not parallelism.») sw.writeln(«Don’t communicate by sharing memory, share memory by communicating.») sw.writeln(«Channels orchestrate; mutexes serialize.») return sw.err // Возвращает ошибку в случае ее возникновения } |
Это более простой и чистый способ для записи текстового файла, но смысл не в этом. Такая же техника может использоваться для создания zip-файлов или для совершенно разных задач. Крупная идея становится значительнее одной техники:
… ошибки являются значениями, и сила языка программирования Go в том, что он может обработать их.
Роб Пайк, «Ошибки — это значения«
Перед вами элегантный способ обработки ошибок в Go.
Вопрос для проверки:
Если бы сообщение об ошибке в Листинге 6 сообщало файлу “Clear is better than clever.”, какие бы событие последовали за этим?
Новые ошибки в программе на Golang
Если функция получает неверные параметры, или если что-то другое идет не так, вы можете создать и вернуть новые значения ошибок, чтобы оповестить вызывающий элемент о проблеме.
Для демонстрации новых ошибок Листинг 7 создает основу для Судоку, что представляет собой сетку 9 на 9. Каждый квадрат сетки может содержать цифру от 1 до 9. Имплементация использует массив с фиксированным размером, ноль указывает на пустой квадрат.
const rows, columns = 9, 9 // Grid является сеткой Судоку type Grid [rows][columns]int8 |
Пакет errors содержит функцию конструктора, что принимает строку для сообщения об ошибке. Используя ее, метод Set
в «Листинге 8» может создать и возвратить ошибку "out of bounds"
.
Проверка параметров в начале метода защищает оставшуюся часть метода от неправильного ввода.
func (g *Grid) Set(row, column int, digit int8) error { if !inBounds(row, column) { return errors.New(«out of bounds») } g[row][column] = digit return nil } |
Функция inBounds
в следующем листинге помогает убедиться, что row
и column
находятся в пределах границ сетки. Она не дает методу Set
забиться лишними деталями.
func inBounds(row, column int) bool { if row < 0 || row >= rows { return false } if column < 0 || column >= columns { return false } return true } |
Наконец функция main
в следующем листинге создает сетку и отображает любую ошибку, возникшую в результате неправильной замены.
func main() { var g Grid err := g.Set(10, 0, 5) if err != nil { fmt.Printf(«An error occurred: %v.n», err) os.Exit(1) } } |
На заметку: Для сообщений ошибок часто используются части предложений, чтобы перед отображением к ним можно было добавить дополнительный текст.
Всегда читайте сообщения об ошибках. Рассматривайте их как часть пользовательского интерфейса программы, будь он для конечных пользователей и разработчиков. Фраза «out of bounds» неплоха, но более точное «outside of grid boundaries» может быть лучше. А сообщение «error 37» вообще ни о чем не говорит.
Вопрос для проверки:
В чем преимущество защит от плохого ввода перед функцией?
Причины каждой ошибки в Go
Многие пакеты Go объявляют и экспортируют переменные для ошибок, которые они могут вернуть. Для использования этого с сеткой Судоку следующий листинг объявляет две переменные для ошибок на уровне пакета.
var ( ErrBounds = errors.New(«out of bounds») ErrDigit = errors.New(«invalid digit») ) |
На заметку: Принято присваивать сообщения об ошибках переменным, что начинаются со слова
Err
.
По объявлении ErrBounds
вы можете изменить метод Set
для возвращения его вместо создания новой ошибки, как показано в следующем коде.
if !inBounds(row, column) { return ErrBounds } |
Если метод Set
возвращает ошибку, вызывающая сторона может различить возможные ошибки и обрабатывать определенные ошибки по-разному, как показано в следующем листинге. Вы можете сравнить ошибку, возвращаемую с переменными ошибки, используя ==
или оператор switch.
var g Grid err := g.Set(0, 0, 15) if err != nil { switch err { case ErrBounds, ErrDigit: fmt.Println(«Les erreurs de paramètres hors limites.») default: fmt.Println(err) } os.Exit(1) } |
На заметку: Конструктор
errors.New
имплементируется через использование указателя, поэтому операторswitch
в предыдущем примере сравнивает адреса памяти, текст не содержит сообщения об ошибке.
Задание для проверки:
Напишите функцию validDigit
и используйте ее, чтобы убедиться, что метод Set
принимает только цифры между 1 и 9.
Настраиваемые типы ошибок в Golang
Каким бы полезным не был errors.New
, иногда нужно, чтобы ошибки описывались не просто сообщением. Go достаточно свободен в этом плане.
Тип error
является встроенным интерфейсом, как показано в следующем примере. Любой тип, что имплементирует метод Error()
для возвращения строки, неявно удовлетворяет интерфейс. В качестве интерфейса возможно создать новые типы ошибок.
type error interface { Error() string } |
Множество ошибок в Golang
Есть несколько причин, по которым цифра не может быть помещена в определенное место в Судоку. В предыдущем разделе мы установили два правила: строки и столбцы находятся внутри сеткии, и цифры находятся в промежутке от 1 до 9. Что будет, если вызывающий элемент передаст множество неверных аргументов?
Вместо возвращения одной ошибки за раз, метод Set
может сделать несколько проверок и вернуть все ошибки сразу. Тип SudokuError
в Листинге 15 является срезом error
. Он удовлетворяет интерфейсу error
с методом, что соединяет ошибки вместе в одну строку.
На заметку: Принято, что настраиваемые типы ошибок вроде
SudokuError
заканчиваются словомError
. Иногда это просто словоError
вродеurl.Error
из пакетаurl
.
type SudokuError []error // Error возвращает одну или несколько ошибок через запятые. func (se SudokuError) Error() string { var s []string for _, err := range se { s = append(s, err.Error()) // Конвертирует ошибки в строки } return strings.Join(s, «, «) } |
Чтобы использовать SudokuError
, метод Set
можно модифицировать для валидации границ и цифр, возвращая обе ошибки сразу, как показано в следующем примере.
func (g *Grid) Set(row, column int, digit int8) error { // Возвращает тип ошибки var errs SudokuError if !inBounds(row, column) { errs = append(errs, ErrBounds) } if !validDigit(digit) { errs = append(errs, ErrDigit) } if len(errs) > 0 { return errs } g[row][column] = digit return nil // Возвращает nil } |
Если ошибок нет, метод Set
возвращает nil
. Это не изменилось по сравнению с Листингом 8, но важно отметить, что пустой срез errs
здесь не возвращается. Для подробностей можете почитать об интерфейсах nil.
Сигнатура метода для Set
также не изменилась по сравнению с Листингом 8. Всегда используйте тип интерфейса error
при возвращении ошибок, а не конкретные типы вроде SudokuError
.
Вопрос для проверки:
Что произойдет, если метод Set
успешно вернет пустой срез errs
?
Утверждение типа в Go
Так как Листинг 16 конвертирует SudokuError
в тип интерфейса error
перед его возвращением, может возникнуть вопрос, как получить доступ к отдельным ошибкам. Решением станет утверждение типа, или type assertion. Используя утверждение типа, вы можете конвертировать интерфейс в конкретный базовый тип.
Утверждение типа в Листинге 17 утверждает err
для типа SudokuError
через код err.(SudokuError)
. Это так и есть, то ok
будет истинным, а err
будет SudokuError
, давая доступ к срезам ошибок в данном случае. Помните, что отдельные ошибки для SudokuError
являются переменными ErrBounds
и ErrDigit
, что могут сравниваться в случае необходимости.
var g Grid err := g.Set(10, 0, 15) if err != nil { if errs, ok := err.(SudokuError); ok { fmt.Printf(«%d error(s) occurred:n», len(errs)) for _, e := range errs { fmt.Printf(«- %vn», e) } } os.Exit(1) } |
В выводе предыдущего кода будут следующие ошибки:
2 error(s) occurred: — out of bounds — invalid digit |
На заметку: Если тип удовлетворяет нескольким интерфейсам, утверждение типа также может конвертировать из одного интерфейса в другой.
Вопрос для проверки:
Что делает утверждение типа err.(SudokuError)
?
Принцип работы panic в Golang
При обработке ошибок некоторые языки программирования сильно полагаются на исключения. В Go нет исключений, но в нем есть похожий механизм, который называется panic
. При задействовании panic
в программе происходит сбой. Это похоже на случаи необработанных исключений в других языках.
Есть ли исключения в Golang?
Исключения в других языках значительно отличаются от значений ошибок в Go. Это заметно как в поведении, так и в имплементации.
Если функция выдает исключение, и никто не собирается его перехватить, это исключение доходит до вызывающей функции, затем до вызывающей ту функцию и так далее, пока достигает вершины стека вызовов (например, функция main
).
Исключения — это стиль обработки ошибок, который можно считать включенным. Часто они не занимают код, тогда как выбор обработки исключений может привлекать изрядное количество специализированного кода. Это связано с тем, что вместо использования существующих возможностей языка исключения обычно имеют специальные ключевые слова, такие как try
, catch
, throw
, finally
, raise
, rescue
, except
и так далее.
Значения ошибок в Go предоставляют простую, гибкую альтернативу исключениям, которые могут помочь вам создать надежное программное обеспечение. Игнорирование значений ошибок в Go — это сознательное решение, которое становится очевидным каждому, кто читает полученный код.
Вопрос для проверки:
В чем преимущество значений ошибок Go по сравнению с исключениями?
Как использовать panic в Golang
Как упоминалось ранее, в Go есть механизм, похожий на исключения, что называется panic
. В то время, как неправильная цифра в Судоку в другом языке может стать причиной исключения, panic
в Go является редкостью.
При осознании того, что, отправившись в поездку, вы забыли полотенце, вы можете запаниковать. Аргумент, переданный panic
, может быть любого типа, не только строкой, как показано ниже:
panic(«Я забыл свое полотенце») |
На заметку: Хотя значения ошибок обычно предпочтительнее
panic
,panic
часто лучше, чемos.Exit
в том, чтоpanic
запустит любую отсроченную функцию, аos.Exit
этого делать не станет.
В некоторых ситуациях Go предпочтет panic
вместо значений ошибок, это может быть деление на ноль:
var zero int _ = 42 / zero // Runtime error: integer divide by zero — целое число делится на ноль |
Вопрос для проверки:
Как программа может использовать panic
?
Тонкости работы с panic в Golang
Чтобы panic
не привел к сбою программы, Go предоставляет функцию recover
, что показано в Листинге 18.
Отсроченные функции выполняются перед возвращением функции, даже в том случае, если задействуется panic
. Если отсроченная функция вызывает recover
, panic
остановится, и программа продолжит выполняться. В таком случае цель у recover похожа на catch
, except
и rescue
в других языках.
defer func() { if e := recover(); e != nil { // Приходит в себя после panic fmt.Println(e) // Выводит: Я забыл свое полотенце } }() panic(«Я забыл свое полотенце») // Приводит к panic |
Данный код использует анонимную функцию.
Вопрос для проверки:
Где может использоваться встроенная функция recover
?
Заключение
- Ошибки являются значениями, что внутренне оперируют с несколькими возвращаемыми значениями и другой частью языка Go;
- Будучи креативным, можно найти множество способов для обработки ошибок;
- Настраиваемые типы ошибок могут удовлетворить интерфейсу
error
; - Ключевое слово
defer
помогает выполнить очистку перед возвращением функции; - Утверждение типа может конвертировать интерфейс в конкретный тип или другой интерфейс;
- Не паникуйте — изучите ошибку.
Итоговое задание для проверки:
В стандартной библиотеке Go есть функция для парсинга веб адресов (см. golang.org/pkg/net/url/#Parse). Отобразите ошибку, которая возникает, когда url.Parse
используется для неправильного веб адреса вроде того, что содержит пробелы: https://a b.com/
.
Используйте специальный символ %#v
с Printf для изучения ошибки. Затем выполните утверждение типа *url.Error
для получения доступа и вывода полей базовой структуры.
На заметку: URL, или Uniform Resource Locator — адрес страницы в Интернете.
Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.
E-mail: vasile.buldumac@ati.utm.md
Образование
Технический Университет Молдовы (utm.md), Факультет Вычислительной Техники, Информатики и Микроэлектроники
- 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»