Что делает форматирование с %w в fmt.Errorf в Go
Что проверяют
Понимаете ли вы разницу между «добавить контекст» и «сохранить причину». Оборачивание ошибок это ежедневная работа на Go, и по ответу видно, умеете ли вы потом эту цепочку разбирать.
Как отвечать
fmt.Errorf с глаголом %w создаёт ошибку, которая помнит вложенную: у результата появляется метод Unwrap, и по цепочке умеют ходить errors.Is и errors.As.
if err := repo.Save(ctx, u); err != nil {
return fmt.Errorf("сохранение пользователя: %w", err)
}
// выше по стеку
if errors.Is(err, sql.ErrNoRows) { /* нет записи */ }
var ve *ValidationError
if errors.As(err, &ve) { /* доступны поля ve */ }%v и %s вставляют только текст: сообщение получится тем же самым, а проверка по цепочке перестанет находить причину. Это и есть суть ответа.
Стоит добавить, что errors.Is сравнивает с сигнальной ошибкой, а errors.As достаёт конкретный тип и требует указатель на переменную этого типа. В одном вызове допустимо несколько %w, тогда развёртка даёт срез ошибок, а для объединения независимых ошибок есть errors.Join.
Взрослый ответ включает цену решения: обёрнутая ошибка становится частью контракта пакета. Если снаружи пишут errors.Is(err, ErrNotFound), поменять внутреннюю ошибку молча уже нельзя.
Где спотыкаются
Сравнивают ошибки через == после оборачивания. Обёртка это другое значение, поэтому сравнение не срабатывает, и нужен errors.Is, который идёт по всей цепочке.
Второе: оборачивают на каждом уровне без нового смысла. В логе получается пять повторов слова «ошибка» и ни одной детали. Контекст добавляют тем, чего нет во вложенной ошибке: имя операции, идентификатор записи.
Третье: подставляют в %w возможный nil. Вызов fmt.Errorf("что-то: %w", nil) возвращает непустую ошибку с текстом про неверный формат, и вызывающий код видит сбой там, где его не было.