В чём семантическая разница между обёрткой ошибки через %w и форматированием через %v при последующей обработке причины?
%w сохраняет исходную ошибку в цепочке обёртки, поэтому errors.Is и errors.As могут добраться до причины. %v только вставляет текст ошибки в сообщение и не сохраняет причинную связь для стандартных механизмов анализа ошибок.
До появления стандартного механизма цепочек ошибок разработчики часто добавляли контекст форматированием сообщения. Такой подход был удобен для журналов, но делал программную обработку ненадёжной: вызывающему коду приходилось сравнивать строки или использовать соглашения конкретных пакетов.
В Go 1.13 появились стандартные средства оборачивания ошибок, а также функции errors.Is и errors.As. Они отделили текстовое описание проблемы от структурированной причинной связи.
Представим, что нижний слой вернул специальную ошибку, например context.Canceled, а промежуточный слой хочет добавить сведения о своей операции. Если он использует %v, сообщение станет информативнее, но исходная ошибка потеряется с точки зрения стандартного API.
Из-за этого верхний слой не сможет надёжно отличить отмену операции от тайм-аута, ошибки доступа или обычного сбоя. Сравнение текстов не решает проблему: сообщения могут меняться, локализоваться и дополняться разными слоями.
В fmt.Errorf спецификатор %w означает, что переданная ошибка становится причиной результата. Такой результат обычно реализует механизм Unwrap, а errors.Is и errors.As проходят по цепочке обёрток.
%v не имеет этого смысла. Он вызывает текстовое форматирование ошибки и создаёт обычное сообщение без доступной для errors.Is или errors.As связи с исходным объектом.
Обе ошибки имеют похожий текст, но только wrapped сохраняет машинно обрабатываемую причинную связь. При этом %w не означает, что ошибку нужно оборачивать всегда: если слой намеренно скрывает внутреннюю реализацию и не хочет обещать её наличие в публичном контракте, он может вернуть новую ошибку без раскрытия причины.
Обёртка должна добавлять контекст, а не менять смысл причины. Если требуется преобразовать внутреннюю ошибку в публичную категорию, лучше явно вернуть стабильную доменную ошибку или определить подходящий метод Is, сохранив понятный контракт для вызывающего кода.
Репозиторий читает запись и получает sql.ErrNoRows. Нужно добавить имя сущности, чтобы в журнале было понятно, какая запись не найдена.
Вариант с возвратом исходной ошибки сохраняет проверяемость через errors.Is, но теряет контекст. Вариант с %v сохраняет контекст в тексте, однако ломает машинную обработку: сервис выше уже не может надёжно определить отсутствие записи.
Выбран вариант с %w: слой возвращает сообщение с именем сущности и сохраняет sql.ErrNoRows в цепочке. В результате журналы остаются информативными, а прикладной код может преобразовать эту ситуацию в ответ «не найдено» без анализа текста ошибки.
Если же sql.ErrNoRows является внутренней деталью репозитория, пакет может сознательно преобразовать её в собственную доменную ошибку. Это уменьшает связанность с драйвером, но требует определить и документировать стабильный способ распознавания новой ошибки.
%w, чтобы errors.Is всегда нашёл исходную ошибку?Нет. Поиск работает только по реально сохранённой цепочке, которую предоставляет обёртка или реализация Unwrap. Если ошибка была превращена в строку, а затем создана заново, исходной связи уже нет. Кроме того, пользовательская реализация Is может задавать специальную семантику совпадения, поэтому проверять нужно контракт конкретного типа.
Да. Нужно вернуть новую ошибку без %w, если раскрытие причины не входит в контракт слоя. Это защищает от зависимости вызывающего кода от деталей реализации, но лишает его возможности классифицировать внутреннюю причину; такой компромисс должен быть осознанным, особенно для библиотечного API.
errors.Is сравнением текста после %v?Текст ошибки предназначен прежде всего для человека и диагностики, а не для устойчивого протокола между слоями. Формулировка может измениться при добавлении контекста, обновлении библиотеки или рефакторинге, не меняющем смысл ошибки. errors.Is опирается на структуру цепочки и поэтому отделяет программную классификацию от нестабильного текста.