[Перевод] Обработка ошибок пакета ошибок Go 1.13

Go

В Go 1.13 представлен расширенныйpackage errors, что примерно нормализует обработку ошибок. Лично я нахожу его API немного запутанным. В этой статье приведены некоторые ссылки на то, как использовать его более эффективно.

создавать ошибки

Ошибки Sentinel такие же, как и раньше. Назовите их ErrXxx и используйте errors.New для их создания.

var ErrFoo = errors.New("foo error")

Типы ошибок в основном такие же, как и раньше. Назовите их XxxError и убедитесь, что у них есть методы Error, соответствующие интерфейсу ошибок.

type BarError struct {
    Reason string
}

func (e BarError) Error() string {
    return fmt.Sprintf("bar error: %s", e.Reason)
}

Если ваш тип ошибки оборачивает другую ошибку, вам необходимо предоставить метод Unwrap.

type BazError struct {
    Reason string
    Inner  error
}

func (e BazError) Error() string {
    if e.Inner != nil {
        return fmt.Sprintf("baz error: %s: %v", e.Reason, e.Inner)
    }
    return fmt.Sprintf("baz error: %s", e.Reason)
}

func (e BazError) Unwrap() error {
    return e.Inner
}

упаковка и возврат ошибок

По умолчанию, когда вы сталкиваетесь с ошибкой в ​​функции и вам нужно вернуть ее вызывающей стороне, вы можете передатьfmt.Errorfиз%wformat, оборачивая ошибки соответствующим контекстом.

func process(j Job) error {
    result, err := preprocess(j)
    if err != nil {
         return fmt.Errorf("error preprocessing job: %w", err)
    }

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

Кроме того, рассмотрите возможность оборачивания ошибок пользовательскими типами ошибок (например, BazError выше) для более сложных вариантов использования.

p := getPriority()
widget, err := manufacture(p, result)
if err != nil {
    return ManufacturingError{Priority: p, Error: err}
}

проверка ошибок

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

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

err := f()
if errors.Is(err, ErrFoo) {
    // you know you got an ErrFoo
    // respond appropriately
}

var bar BarError
if errors.As(err, &bar) {
    // you know you got a BarError
    // bar's fields are populated
    // respond appropriately
}

errors.Is и errors.As попытаются рекурсивно распаковать ошибки, чтобы найти совпадение.этот кодДемонстрирует базовые методы упаковки и проверки ошибок.(Примечание переводчика: нужно зайти в интернет по-научному, и вставить этот код в конец статьи). Проверятьfunc a()в порядке проверки, затем попробуйте изменитьfunc c()Ошибка вернулась, чтобы получить информацию о запущенном процессе.

в видеДокументацияКак уже говорилось, лучше использовать ошибки. Проверить обычные уравнения, например.if err == ErrFoo; Предпочитаю ошибки. Что касается утверждения общих типов, например.if e,ok := err.(MyError), потому что обычная версия не выполняет операцию развертывания. Если вы явно не хотите, чтобы вызывающая сторона разворачивала ошибку, вы можете сделатьfmt.ErrorfПредоставляет различные глаголы форматирования, такие как%v; или не указывайте неправильный типUnwrapметод. Но эти случаи не часты.

Пример

package main

import (
	"errors"
	"fmt"
	"log"
)

func main() {
	i, err := a()
	log.Printf("i=%d err=%v", i, err)
}

//
//
//

func a() (int, error) {
	i, err := b()
	if errors.Is(err, ErrFoo) {
		return 0, fmt.Errorf("tragedy: %w", err)
	}

	var bar BarError
	if errors.As(err, &bar) {
		return 0, fmt.Errorf("comedy: %w", err)
	}

	var baz BazError
	if errors.As(err, &baz) {
		return 0, fmt.Errorf("farce: %w", err)
	}

	return i, nil
}

func b() (int, error) {
	if err := c(); err != nil {
		return 0, fmt.Errorf("error executing c: %w", err)
	}
	return 1, nil
}

func c() error {
	// return ErrFoo
	// return BarError{Reason: "😫"}
	// return BazError{Reason: "☹️"}
	return BazError{Reason: "😟", Inner: ErrFoo}
}

//
//
//

var ErrFoo = errors.New("foo error")

//
//
//

type BarError struct {
	Reason string
}

func (e BarError) Error() string {
	return fmt.Sprintf("bar error: %s", e.Reason)
}

//
//
//

type BazError struct {
	Reason string
	Inner  error
}

func (e BazError) Unwrap() error {
	fmt.Println("fuck")
	return e.Inner
}

func (e BazError) Error() string {
	if e.Inner != nil {
		return fmt.Sprintf("baz error: %s: %v", e.Reason, e.Inner)
	}
	return fmt.Sprintf("baz error: %s", e.Reason)
}