Обработка ошибок в Go
Язык Go предоставляет очень простой механизм обработки ошибок через встроенный интерфейс error.
Обработка ошибок в Go использует подход с явным возвратом ошибок, а не традиционный механизм обработки исключений. Такая архитектура делает логику кода понятнее и упрощает для разработчиков явную обработку ошибок во время компиляции или выполнения.
Обработка ошибок в Go в основном строится вокруг следующих механизмов:
errorинтерфейсСтандартное представление ошибки.- Явное возвращаемое значениеОшибки возвращаются через возвращаемое значение функции.
- Пользовательская ошибкаОшибки создаются пользовательским способом.
panicиrecoverОбработка неустранимых ошибок.
интерфейс error
Стандартная библиотека Go определяет интерфейс error, представляющий абстракцию ошибки.
Тип error — это интерфейсный тип. Вот его определение:
type error interface {
Error() string
}
- Реализация
errorinterface: Всё, что реализуетError()Любой тип, реализующий метод Error(), может использоваться как error. Error()Метод возвращает строку с описанием ошибки.
Использование пакета errors для создания ошибок
В коде мы можем создавать информацию об ошибке, реализуя интерфейсный тип error.
Создайте простую ошибку:
Пример
import (
"errors"
"fmt"
)
func main() {
err := errors.New("this is an error")
fmt.Println(err) // Вывод: this is an error
}
Функции обычно возвращают информацию об ошибке в качестве последнего возвращаемого значения. Для возврата сообщения об ошибке можно использовать errors.New:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// 实现
}
В следующем примере мы передаём отрицательное число в Sqrt и затем получаем объект ошибки, не равный nil. Сравнение этого объекта с nil возвращает true, поэтому вызывается fmt.Println для вывода ошибки (когда пакет fmt обрабатывает ошибку, он вызывает метод Error). См. пример кода вызова ниже:
result, err:= Sqrt(-1)
if err != nil {
fmt.Println(err)
}
Явный возврат ошибок
В Go ошибки обычно возвращаются как возвращаемые значения функций, и разработчикам необходимо явно проверять и обрабатывать их.
Явный возврат ошибок:
Пример
import (
"errors"
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}
Вывод:
Error: division by zero
Пользовательская ошибка
Определяя пользовательские типы, можно расширить интерфейс error.
Пользовательские типы ошибок:
Пример
import (
"fmt"
)
type DivideError struct {
Dividend int
Divisor int
}
func (e *DivideError) Error() string {
return fmt.Sprintf("cannot divide %d by %d", e.Dividend, e.Divisor)
}
func divide(a, b int) (int, error) {
if b == 0 {
return 0, &DivideError{Dividend: a, Divisor: b}
}
return a / b, nil
}
func main() {
_, err := divide(10, 0)
if err != nil {
fmt.Println(err) // Вывод: cannot divide 10 by 0
}
}
Пакет fmt и форматирование ошибок.
fmtПакет предоставляет поддержку форматированного вывода для ошибок:
%v: Формат по умолчанию.%+v: Если поддерживается, отображать подробную информацию об ошибке.%sВывод в виде строки.
Пример
import (
"fmt"
)
// Определить структуру DivideError
type DivideError struct {
dividee int
divider int
}
// Реализовать интерфейс `error`
func (de *DivideError) Error() string {
strFormat := `
Невозможно продолжить, делитель равен нулю.
делимое: %d
делитель: 0
`
return fmt.Sprintf(strFormat, de.dividee)
}
// Определите функцию для деления типа `int`
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
if varDivider == 0 {
dData := DivideError{
dividee: varDividee,
divider: varDivider,
}
errorMsg = dData.Error()
return
} else {
return varDividee / varDivider, ""
}
}
func main() {
// Обычный случай
if result, errorMsg := Divide(100, 10); errorMsg == "" {
fmt.Println("100/10 = ", result)
}
// Возвращает сообщение об ошибке, когда делитель равен нулю
if _, errorMsg := Divide(100, 0); errorMsg != "" {
fmt.Println("errorMsg is: ", errorMsg)
}
}
Запустите приведённую выше программу, вывод:
100/10 = 10
errorMsg is:
Cannot proceed, the divider is zero.
dividee: 100
divider: 0
Использование errors.Is и errors.As
Начиная с Go 1.13,errorsПакет вводитerrors.Isиerrors.AsИспользуется для обработки цепочек ошибок:
errors.Is
Проверьте, является ли ошибка конкретной ошибкой или была обёрнута из этой ошибки.
Пример
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("not found")
func findItem(id int) error {
return fmt.Errorf("ошибка базы данных: %w", ErrNotFound)
}
func main() {
err := findItem(1)
if errors.Is(err, ErrNotFound) {
fmt.Println("Элемент не найден")
} else {
fmt.Println("Другая ошибка:", err)
}
}
errors.As
Преобразуйте ошибку в конкретный тип для дальнейшей обработки.
Пример
import (
"errors"
"fmt"
)
type MyError struct {
Code int
Msg string
}
func (e *MyError) Error() string {
return fmt.Sprintf("Код: %d, Сообщение: %s", e.Code, e.Msg)
}
func getError() error {
return &MyError{Code: 404, Msg: "Не найдено"}
}
func main() {
err := getError()
var myErr *MyError
if errors.As(err, &myErr) {
fmt.Printf("Пользовательская ошибка - Код: %d, Сообщение: %s\n", myErr.Code, myErr.Msg)
}
}
panic и recover
panic в Go используется для обработки неустранимых ошибок, а recover используется для восстановления после panic.
panic:
- Вызывает сбой программы и выводит информацию о стеке.
- Часто используется, когда программа не может продолжать работу.
recover:
- catch
panicпредотвращая аварийное завершение программы.
Пример
import "fmt"
func safeFunction() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
panic("something went wrong")
}
func main() {
fmt.Println("Starting program...")
safeFunction()
fmt.Println("Program continued after panic")
}
Запустите приведённый выше код, вывод:
Starting program... Recovered from panic: something went wrong Program continued after panicДругие расширения