История вопроса:
До Go 1.13 ошибка была простым интерфейсом. Для дополнительного контекста ошибки часто создавали свои типы, но структурированного механизма вложения, как в modern Java или C#, не было. С появлением Go 1.13 был введён стандартный способ обёртывания (wrapping) ошибок через функции fmt.Errorf и выявления корневой причины (errors.Is/errors.As).
Проблема:
В сложных приложениях, где ошибка может возникать на разных уровнях стека, важно не терять контекст при возврате ошибки из глубины кода. Иначе дебаг становится трудным и невозможно понять, где в цепочке произошёл сбой.
Решение:
Go предлагает оборачивать ошибки в новые объекты ошибки, которые содержат причину. Для этого используется %w в fmt.Errorf, а для проверки глубинных причин — errors.Is или errors.As из пакета errors.
Пример кода:
import ( "errors" "fmt" ) var ErrNotFound = errors.New("not found") func getData() error { return fmt.Errorf("service database: %w", ErrNotFound) } func main() { err := getData() if errors.Is(err, ErrNotFound) { fmt.Println("Обнаружена причина: не найдено") } else { fmt.Println("Другая ошибка:", err) } }
Ключевые особенности:
%w для вложения ошибок через fmt.Errorf.errors.Is и errors.As.Какой оператор форматирования нужен для обёртывания ошибок через fmt.Errorf?
Верно использовать %w, а не %v — только %w даёт поддержку unwrap.
Пример кода:
fmt.Errorf("ошибка: %w", err)
Можно ли вручную создавать цепочки ошибок без fmt.Errorf и всё равно их выявлять через errors.Is?
Нет, нужно реализовать интерфейс Unwrap, иначе стандартные функции не развернут цепочку.
Пример кода интерфейса:
type wrappedError struct { msg string err error } func (w wrappedError) Error() string { return w.msg + ": " + w.err.Error() } func (w wrappedError) Unwrap() error { return w.err }
Возвращают ли ошибки значения nil после wrapping, если вложенная ошибка была nil?
Нет, если вложенная ошибка nil, то обёрнутая ошибка тоже nil только при корректном использовании. Но если сделать wrapper-структуру вручную, где поле ошибки nil, будет не nil. Это часто приводит к путанице при проверке.
%w, а только %v — теряется возможность анализа цепочки.Unwrap() для своих структур ошибок.errors.Is/As, а только сравнивать ошибку напрямую.В приложении просто возвращали новую ошибку с текстом на каждом уровне:
return errors.New("ошибка записи в БД")
Плюсы:
Минусы:
Использовали обёртывание ошибок с %w и анализ через errors.Is:
if errors.Is(err, ErrNotFound) { return fmt.Errorf("ошибка уровня сервиса: %w", err) }
Плюсы:
Минусы: