Показаны сообщения с ярлыком Пакет errors в Golang. Показать все сообщения
Показаны сообщения с ярлыком Пакет errors в Golang. Показать все сообщения

суббота, 21 ноября 2020 г.

Go style guides: обертывание ошибок, обработка ошибок утверждения типа

Обертывание ошибок

Есть три основных варианта распространения ошибок в случае сбоя вызова:

  • Верните исходную ошибку, если нет дополнительного контекста для добавления и вы хотите сохранить исходный тип ошибки.
  • Добавьте контекст, используя "github.com/pkg/errors".Wrap, чтобы сообщение об ошибке предоставляло больше контекста и "github.com/pkg/errors".Cause можно использовать для извлечения исходной ошибки.
  • Используйте fmt.Errorf, если вызывающим абонентам не нужно обнаруживать или обрабатывать этот конкретный случай ошибки.

Рекомендуется добавлять контекст, где это возможно, чтобы вместо неопределенной ошибки, такой как "connection refused" ("соединение отклонено"), вы получали более полезные ошибки, такие как "call service foo: connection refused" ("вызов службы foo: соединение отклонено").

При добавлении контекста к возвращаемым ошибкам сохраняйте краткость контекста, избегая фраз вроде "failed to" ("не удалось"), которые констатируют очевидное и накапливаются по мере того, как ошибка просачивается через стек.

Менее удачный вариант:

s, err := store.New()
if err != nil {
    return fmt.Errorf("failed to create new store: %s", err)
}

Вывод:

failed to x: failed to y: failed to create new store: the error

Более удачный вариант:

s, err := store.New()
if err != nil {
    return fmt.Errorf("new store: %s", err)
}

Вывод:

x: y: new store: the error

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

Обработка ошибок утверждения типа

Форма единственного возвращаемого значения утверждения типа вызовет панику из-за неправильного типа. Поэтому всегда используйте идиому "comma ok".

Неудачный вариант:

t := i.(string)

Более удачный вариант:

t, ok := i.(string)
if !ok {
    // корректно обрабатываем ошибку
}


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


четверг, 19 ноября 2020 г.

Go style guides: типы ошибок

Существуют различные варианты объявления ошибок:

  • errors.New для ошибок с простыми статическими строками
  • fmt.Errorf для форматированных строк ошибок
  • настраиваемые типы, реализующие метод Error()
  • обернутые ошибки с помощью "github.com/pkg/errors".Wrap

При возврате ошибок учитывайте следующее, чтобы определить лучший выбор:

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

Если клиенту необходимо обнаружить ошибку, и вы создали простую ошибку, используя errors.New, используйте var для ошибки.

Менее удачный вариант:

// package foo

func Open() error {
    return errors.New("could not open")
}

// package bar

func use() {
    if err := foo.Open(); err != nil {
        if err.Error() == "could not open" {
            // обработка
        } else {
            panic("unknown error")
        }
    }
}

Более удачный вариант:

// package foo

var ErrCouldNotOpen = errors.New("could not open")

func Open() error {
    return ErrCouldNotOpen
}

// package bar

if err := foo.Open(); err != nil {
    if err == foo.ErrCouldNotOpen {
        // обработка
    } else {
        panic("unknown error")
    }
}

Более удачный вариант с версии Go 1.13:

// package foo

var ErrCouldNotOpen = errors.New("could not open")

func Open() error {
    return ErrCouldNotOpen
}

// package bar

if err := foo.Open(); err != nil {
    if errors.Is(err, foo.ErrCouldNotOpen) {
        // обработка
    } else {
        panic("unknown error")
    }
}

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

Менее удачный вариант:

func open(file string) error {
    return fmt.Errorf("file %q not found", file)
}

func use() {
    if err := open("testfile.txt"); err != nil {
        if strings.Contains(err.Error(), "not found") {
            // обработка
        } else {
            panic("unknown error")
        }
    }
}

Более удачный вариант:

type errNotFound struct {
    file string
}

func (e errNotFound) Error() string {
    return fmt.Sprintf("file %q not found", e.file)
}

func open(file string) error {
    return errNotFound{file: file}
}

func use() {
    if err := open("testfile.txt"); err != nil {
        if _, ok := err.(errNotFound); ok {
            // обработка
        } else {
            panic("unknown error")
        }
    }
}

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

// package foo

type errNotFound struct {
    file string
}

func (e errNotFound) Error() string {
    return fmt.Sprintf("file %q not found", e.file)
}

func IsNotFoundError(err error) bool {
    _, ok := err.(errNotFound)
    return ok
}

func Open(file string) error {
    return errNotFound{file: file}
}

// package bar

if err := foo.Open("foo"); err != nil {
    if foo.IsNotFoundError(err) {
        // handle
    } else {
        panic("unknown error")
    }
}


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


вторник, 30 июня 2020 г.

Пакет errors в Golang

Пакет errors реализует функции для манипулирования ошибками.

Функция New создает ошибки, единственным содержимым которых является текстовое сообщение.

Функции Unwrap, Is и As работают с ошибками, которые могут переносить другие ошибки. Ошибка переносит другую ошибку, если ее тип имеет метод

Unwrap() error

Если e.Unwrap() возвращает ненулевую ошибку w, то мы говорим, что e переносит w.

Unwrap распаковывает упакованные ошибки. Если тип его аргумента имеет метод Unwrap, он вызывает метод один раз. В противном случае возвращается nil.

Простой способ создать упакованные ошибки - вызвать fmt.Errorf и применить глагол %w к аргументу ошибки:

errors.Unwrap(fmt.Errorf("... %w ...", ..., err, ...))

возвращает err.

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

if errors.Is(err, os.ErrExist)

предпочтительнее чем

if err == os.ErrExist

потому что первый преуспеет, если err обернет os.ErrExist.

As разворачивает свой первый аргумент последовательно ищет ошибку, которая может быть назначена второму аргументу, который должен быть указателем. Если это успешно, выполняется назначение и возвращает true. В противном случае возвращается false. Форма

var perr *os.PathError
if errors.As(err, &perr) {
    fmt.Println(perr.Path)
}

предпочтительнее чем

if perr, ok := err.(*os.PathError); ok {
    fmt.Println(perr.Path)
}

потому что первый будет успешным, если err обернет *os.PathError.

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

package main

import (
    "fmt"
    "time"
)

// MyError это реализация error 
// которая включает время и сообщение.
type MyError struct {
    When time.Time
    What string
}

func (e MyError) Error() string {
    return fmt.Sprintf("%v: %v", e.When, e.What)
}

func oops() error {
    return MyError{
        time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC),
        "the file system has gone away",
    }
}

func main() {
    if err := oops(); err != nil {
        fmt.Println(err)
    }
}

Вывод:

1989-03-15 22:30:00 +0000 UTC: the file system has gone away

Функция New

func New(text string) error

New возвращает ошибку, которая форматируется как заданный текст. Каждый вызов New возвращает отдельное значение ошибки, даже если текст идентичен.

package main

import (
    "errors"
    "fmt"
)

func main() {
    err := errors.New("Неожиданная ошибка")
    if err != nil {
        fmt.Print(err)
    }
}

Вывод:

Неожиданная ошибка

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

package main

import (
    "fmt"
)

func main() {
    const name, id = "бублик", 117
    err := fmt.Errorf("пользователь %q (id %d) не найден", name, id)
    if err != nil {
        fmt.Print(err)
    }
}

Вывод:

пользователь "бублик" (id 117) не найден

Функция Is (с версии Go 1.13)

func Is(err, target error) bool

Is сообщает, соответствует ли target ошибке какая-либо ошибка в цепочке err.

Цепочка состоит из самой err, за которой следует последовательность ошибок, получаемых повторным вызовом Unwrap.

Считается, что ошибка соответствует цели, если она равна этой цели или если она реализует метод Is(error) bool, такой что Is(target) возвращает true.

Тип ошибки может предоставлять метод Is, поэтому его можно рассматривать как эквивалент существующей ошибки. Например, если MyError определяет

func (m MyError) Is(target error) bool { return target == os.ErrExist }

затем Is (MyError{}, os.ErrExist) возвращает значение true. syscall.Errno.Is - пример в стандартной библиотеке.

Функция As (с версии Go 1.13)

func As(err error, target interface{}) bool

As находит первую ошибку в цепочке err, которая соответствует target, и если находит, то устанавливает target равным этому значению ошибки и возвращает true. В противном случае возвращается false.

Цепочка состоит из самой err, за которой следует последовательность ошибок, получаемых повторным вызовом Unwrap.

Ошибка соответствует цели, если конкретное значение ошибки присваивается значению, на которое указывает цель, или если ошибка имеет метод As(interface{}) bool, такой что As(target) возвращает true. В последнем случае метод As отвечает за установку цели.

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

As паникует, если target не является ненулевым указателем ни на тип, который реализует error, ни на любой тип интерфейса.

Пример использования As

package main

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

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

}

Вывод:

Failed at path: non-existing

Функция Unwrap

func Unwrap(err error) error

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


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