Модульні уроки (2/5)
Обробка ідіоматичних помилок
Обробка помилок у Go чітка: жодних винятків, жодних спроб/перехоплень. Кожна функція, яка може вийти з ладу, повертає error як своє останнє значення, і вирішує виклик. У цій главі зібрано ідіоматичні шаблони для надійного створення, поширення та перевірки помилок.
Золоте правило
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("config %s: %w", path, err)
}Чотири принципи:
errorє останнім поверненням.- Негайно перевірте за допомогою
if err != nil. - Додайте контекст перед розповсюдженням (з
fmt.Errorf+%w). - Ніколи не ігноруйте помилку: або обробіть її, зареєструйте або поверніть її.
_ = file.Close() // EXPLICITLY ignored (you know what you're doing)Дозорні помилки
Дозорна помилка — це експортована глобальна змінна var ErrXxx = errors.New("..."), з якою абонент може порівняти:
package store
var ErrNotFound = errors.New("not found")
func Get(key string) (string, error) {
if !exists(key) {
return "", ErrNotFound
}
// ...
}Абонент використовує errors.Is (НЕ ==), який проходить по ланцюжку перенесення:
v, err := store.Get(k)
if errors.Is(err, store.ErrNotFound) {
// handle 404
}Стандартні приклади: io.EOF, sql.ErrNoRows, os.ErrNotExist.
Спеціальні введені помилки
Щоб передавати структуровані дані (код HTTP, недійсне поле тощо), визначте тип:
type ValidationError struct {
Field string
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}Абонент використовує errors.As для безпечного переведення вздовж ланцюжка:
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:
if err := db.Query(); err != nil {
return fmt.Errorf("load users: %w", err)
}Без %w (з використанням %v або %s) ви отримуєте багатший рядок, але втрачаєте ланцюжок: абонент більше не може розпізнавати певні помилки.
Правила для %w:
- Лише один на виклик
fmt.Errorf(для мультиперенесення потрібенerrors.Join). - Аргумент ПОВИНЕН бути
error, а не рядком. - Додайте контекст, не дублюйте оригінальне повідомлення.
// 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:
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
Ніколи для очікуваних помилок. Тільки в трьох випадках:
- Невиправна помилка: інваріант порушено в місці, яке «не може статися» (
panic("unreachable")). - Помилка ініціалізації в
init()/main()окремої програми (відсутня критична конфігурація → швидка помилка). - API бібліотеки, який явно документує це: наприклад,
regexp.MustCompile(паніка, якщо шаблон недійсний — прийнятно, оскільки шаблон є константою часу збирання).
Усе інше error. Не використовуйте panic як ярлик для поширення: це порушує композицію, пропускає defer Close() та перевантажує абонента.
вправи
Оберніть помилку os.ReadFile через fmt.Errorf, використавши слово %w і додавши його до конкурсу «config:» (con il path).
Рішення доступне після 3 спроб
Usa errors.Is per verificare se err nella catena di wrap corrisponde alla sentinella ErrNotFound e stampa 'manca!' in tal caso.
Рішення доступне після 3 спроб
Яка ідіоматика використовує паніку в бібліотеці Go?
func F(x int) {
// ??? panic(...)
}