Перейти до основного вмісту
Модуль 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:» (con il path).

Завантаження редактора…

Рішення доступне після 3 спроб

вправи#go.m10.l2.e2
Спроби: 0Завантаження…

Usa errors.Is per verificare se err nella catena di wrap corrisponde alla sentinella ErrNotFound e stampa 'manca!' in tal caso.

Завантаження редактора…

Рішення доступне після 3 спроб

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

Яка ідіоматика використовує паніку в бібліотеці Go?

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