Перейти к основному содержимому
Модуль 10 · Урок 2 из 547/50 в курсе~14 min
Уроки модуля (2/5)

Идиоматическая обработка ошибок

Обработка ошибок в Go очевидна: никаких исключений, никаких попыток/перехвата. Каждая функция, которая может завершиться неудачно, возвращает error в качестве своего последнего значения, и решение принимает вызывающая сторона. В этой главе собраны идиоматические шаблоны для надежного создания, распространения и проверки ошибок.

Золотое правило

Go
data, err := os.ReadFile(path)
if err != nil {
    return fmt.Errorf("config %s: %w", path, err)
}

Четыре принципа:

  1. error — последний возврат.
  2. Немедленно проверьте с помощью if err != nil.
  3. Добавьте контекст перед распространением (с помощью fmt.Errorf + %w).
  4. Никогда не игнорируйте ошибку: либо обработайте ее, запишите в журнал, либо верните ее.
Go
_ = file.Close() // EXPLICITLY ignored (you know what you're doing)

Дозорные ошибки

Дозорная ошибка — это экспортированная глобальная переменная var ErrXxx = errors.New("..."), с которой вызывающий объект может сравнивать:

Go
package store

var ErrNotFound = errors.New("not found")

func Get(key string) (string, error) {
    if !exists(key) {
        return "", ErrNotFound
    }
    // ...
}

Вызывающий использует errors.Is (НЕ ==), который проходит по цепочке переноса:

Go
v, err := store.Get(k)
if errors.Is(err, store.ErrNotFound) {
    // handle 404
}

Стандартные примеры: io.EOF, sql.ErrNoRows, os.ErrNotExist.

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

Для переноса структурированных данных (HTTP-код, недопустимое поле и т. д.) определите тип:

Go
type ValidationError struct {
    Field string
    Msg   string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}

Вызывающая сторона использует errors.As для безопасного понижения уровня по цепочке:

Go
var ve *ValidationError
if errors.As(err, &ve) {
    fmt.Println("invalid field:", ve.Field)
}

errors.As проходит по цепочке Unwrap(), пока не обнаружит ошибку, связанную с переданным указателем.

Обертывание с помощью %w

fmt.Errorf с глаголом %w создает ошибку, которая заворачивает исходную ошибку, сохраняя ее для errors.Is/errors.As:

Go
if err := db.Query(); err != nil {
    return fmt.Errorf("load users: %w", err)
}

Без %w (с использованием %v или %s) вы получаете более богатую строку, но теряете цепочку: вызывающий объект больше не может распознавать определенные ошибки.

Правила для %w:

  • Только один на вызов fmt.Errorf (для многократного переноса требуется errors.Join).
  • Аргумент ДОЛЖЕН быть error, а не строкой. – Добавьте контекст, не дублируйте исходное сообщение.
Go
// bad: duplicates
return fmt.Errorf("error: %w", err) // "error: file not found"

// good: useful context
return fmt.Errorf("load config %s: %w", path, err)

errors.Join для нескольких ошибок

Начиная с Go 1.20, errors.Join(errs...) объединяет несколько ошибок в одну, благодаря чему их все можно обнаружить с помощью errors.Is/As:

Go
var errs []error
for _, item := range items {
    if err := process(item); err != nil {
        errs = append(errs, fmt.Errorf("item %s: %w", item.ID, err))
    }
}
if len(errs) > 0 {
    return errors.Join(errs...)
}

Когда использовать panic

Никогда для ожидаемых ошибок. Только в трёх случаях:

  1. Неустранимая ошибка: инвариант нарушен в месте, которое «не может произойти» (panic("unreachable")).
  2. Сбой инициализации в init()/main() автономной программы (отсутствует критическая конфигурация → быстрый сбой).
  3. Библиотечный API, который явно документирует это: например, regexp.MustCompile (паника, если шаблон недействителен — приемлемо, поскольку шаблон является константой времени сборки).

Все остальное error. Не используйте panic в качестве ярлыка для распространения вверх: это нарушает композицию, пропускает defer Close() и перегружает вызывающую программу.

Упражнения

Упражнение#go.m10.l2.e1
Попыток: 0Загрузка…

Оберните ошибку os.ReadFile с fmt.Errorf, используя слово %w и добавляя соревнование 'config:' (с путем).

Загрузка редактора…

Решение доступно после 3 попыток

Упражнение#go.m10.l2.e2
Попыток: 0Загрузка…

Ошибки США. Для проверки есть ошибка в катене ди обертки, которая соответствует всем дозорным ErrNotFound и штампу «manca!» в таль случай.

Загрузка редактора…

Решение доступно после 3 попыток

Quiz#go.m10.l2.e3
Готово

Когда идиоматически используется паника в библиотеке. Идти?

Go
func F(x int) {
  // ??? panic(...)
}
Варианты ответа