четверг, 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")
    }
}


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


Комментариев нет:

Отправить комментарий