Обработка ошибок в Go

Язык Go предоставляет очень простой механизм обработки ошибок через встроенный интерфейс error.

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

Обработка ошибок в Go в основном строится вокруг следующих механизмов:

  1. errorинтерфейсСтандартное представление ошибки.
  2. Явное возвращаемое значениеОшибки возвращаются через возвращаемое значение функции.
  3. Пользовательская ошибкаОшибки создаются пользовательским способом.
  4. panicиrecoverОбработка неустранимых ошибок.

интерфейс error

Стандартная библиотека Go определяет интерфейс error, представляющий абстракцию ошибки.

Тип error — это интерфейсный тип. Вот его определение:

type error interface {
    Error() string
}
  • Реализацияerrorinterface: Всё, что реализуетError()Любой тип, реализующий метод Error(), может использоваться как error.
  • Error()Метод возвращает строку с описанием ошибки.

Использование пакета errors для создания ошибок

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

Создайте простую ошибку:

Пример

package main

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 ошибки обычно возвращаются как возвращаемые значения функций, и разработчикам необходимо явно проверять и обрабатывать их.

Явный возврат ошибок:

Пример

package main

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.

Пользовательские типы ошибок:

Пример

package main

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Вывод в виде строки.

Пример

package main

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

Проверьте, является ли ошибка конкретной ошибкой или была обёрнута из этой ошибки.

Пример

package main

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

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

Пример

package main

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:

  • catchpanicпредотвращая аварийное завершение программы.

Пример

package main

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
Другие расширения