Предположим, ваша функция должна возвращать детализированные Recoverable и Fatal ошибки. Как это реализовано в пакете net? Как это надо делать в современном Go?go-87

Как это реализовано в пакете net

В стандартном пакете net используется классический подход с типами ошибок, реализующими определенные интерфейсы:

// Пример из пакета net
type Error interface {
    error
    Timeout() bool   // Recoverable - можно повторить операцию
    Temporary() bool // Recoverable - временная ошибка
}

// Пример использования
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
    // Повторяем операцию после задержки
}

Особенности подхода net:

  1. Проверка через type assertion
  2. Два метода для классификации:
    • Timeout() - ошибка по таймауту
    • Temporary() - временная ошибка
  3. Если оба false - считается fatal ошибкой

Современные практики Go

1. Использование sentinel ошибок с Is/As

var (
    ErrFatal      = errors.New("fatal error")
    ErrRecoverable = errors.New("recoverable error")
)

func Process() error {
    if condition {
        return fmt.Errorf("%w: description", ErrRecoverable)
    }
    return ErrFatal
}

// Проверка
err := Process()
if errors.Is(err, ErrRecoverable) {
    // Повторяем операцию
}

2. Кастомные типы ошибок

type ErrorKind int

const (
    KindRecoverable ErrorKind = iota
    KindFatal
)

type MyError struct {
    Kind    ErrorKind
    Message string
}

func (e *MyError) Error() string {
    return e.Message
}

func (e *MyError) IsRecoverable() bool {
    return e.Kind == KindRecoverable
}

3. Использование интерфейсов

type RecoverableError interface {
    error
    Recoverable() bool
}

type myError struct {
    recoverable bool
    msg         string
}

func (e *myError) Error() string { return e.msg }
func (e *myError) Recoverable() bool { return e.recoverable }

func Process() error {
    return &myError{recoverable: true, msg: "retry later"}
}

// Проверка
err := Process()
var re RecoverableError
if errors.As(err, &re) && re.Recoverable() {
    // Обработка recoverable
}

Лучшие практики для нового кода

  1. Используйте errors.Is/errors.As вместо прямого type assertion
  2. Явно разделяйте типы ошибок через разные переменные/типы
  3. Добавляйте контекст с fmt.Errorf и %w
  4. Для сложных сценариев создавайте кастомные типы ошибок
  5. Документируйте какие ошибки являются recoverable

Пример комплексного решения:

func HandleError(err error) {
    switch {
    case errors.Is(err, ErrNetworkTimeout):
        log.Printf("Recoverable network error: %v", err)
        time.Sleep(retryDelay)
        RetryOperation()
    case errors.Is(err, ErrInvalidInput):
        log.Printf("Fatal error: %v", err)
        os.Exit(1)
    default:
        log.Printf("Unknown error: %v", err)
    }
}

Резюмируем

современный Go предлагает гибкие механизмы для классификации ошибок через комбинацию sentinel errors, кастомных типов и интерфейсов, с проверкой через errors.Is/As. Это более выразительно и поддерживаемо, чем классический подход net пакета.