Когда пользовательскому типу ошибки стоит реализовать fmt.Formatter, а не помещать подробности в Error()?
fmt.Formatter стоит реализовать, когда одной ошибки нужны разные представления: короткое стабильное сообщение для обычного вывода и расширенная диагностика для специального форматирования, например %+v. Метод Error() при этом должен оставаться компактным, предсказуемым и пригодным для обычного журналирования.
Реализация fmt.Formatter меняет только формат представления. Она не влияет на сопоставление через errors.Is, извлечение через errors.As или цепочку причин через Unwrap.
В Go ошибка определяется небольшим интерфейсом с методом Error() string. Такой подход отделяет передачу ошибки от её конкретного типа и позволяет использовать одно значение в разных слоях приложения.
Пакет fmt дополняет базовый механизм интерфейсом форматирования. Это решает практическую проблему, когда краткое сообщение ошибки недостаточно для диагностики, но добавление всех подробностей в Error() ухудшает читаемость логов и делает текст нестабильным.
Если безусловно помещать стек вызовов, внутренние идентификаторы или технические поля в Error(), одно и то же сообщение начинает использоваться сразу как пользовательский текст, лог и диагностическая информация. Это усложняет чтение логов и может превратить формат сообщения в неявный контракт.
Если же менять состояние ошибки во время форматирования или не обрабатывать разные варианты форматирования, повторный вывод может давать разные результаты, а диагностика — зависеть от конкретного вызова fmt.
Обычно Error() возвращает короткое описание, достаточное для общего контекста. Format может добавить детали при явно выбранном формате, например при %+v, а для остальных случаев должен выдавать понятное базовое представление.
В примере %v даёт краткое сообщение, а %+v — сообщение с диагностической деталью. Форматтер не должен вызывать форматирование того же значения через fmt, иначе легко получить бесконечную рекурсию.
Важно обрабатывать форматтер как часть поведения типа: он должен быть детерминированным, не менять поля ошибки и не выполнять побочных действий. Подробное представление не является механизмом защиты данных: если оно может содержать секреты, доступ к нему нужно контролировать отдельно.
Если задача состоит только в добавлении контекста к причине, предпочтительнее wrapping через %w, а не fmt.Formatter. Форматтер отвечает за вид вывода, тогда как wrapping сохраняет причинно-следственную связь для errors.Is и errors.As.
Сервис возвращает ошибку сбоя внешнего API. Для клиента нужно короткое сообщение, для технического журнала — код операции, адрес узла и диагностическая причина.
Первый вариант — добавить все поля в Error(). Он прост, но загрязняет пользовательские сообщения и создаёт зависимость от их точного формата. Второй вариант — хранить диагностику отдельно и вручную собирать её в каждом месте логирования; это уменьшает связанность с fmt, но приводит к дублированию.
Выбран вариант с коротким Error() и fmt.Formatter для явно запрошенного расширенного представления. Он сохраняет единый тип ошибки, не меняет семантику errors.Is и errors.As, а диагностический вывод доступен только в местах, где он действительно нужен. При этом чувствительные данные не включаются в форматтер без дополнительной проверки.
fmt.Formatter возможность обнаружить исходную ошибку?Нет. fmt.Formatter определяет только способ отображения значения. Проверки errors.Is и errors.As используют методы сопоставления, тип ошибки и цепочку, возвращаемую Unwrap, поэтому форматирование не должно использоваться вместо проектирования причин ошибки.
%+v безопасным способом скрыть детали от пользователя?Нет. Это лишь соглашение о формате вывода. Любой код, имеющий значение ошибки, может явно выбрать расширенное форматирование, а лог или трассировка могут попасть в неподходящее место. Безопасность достигается разделением публичных и внутренних данных, контролем логирования и редактированием чувствительных значений, а не самим форматом %+v.
Format реализован неполно?Тип всё равно будет форматироваться через этот метод, поэтому пропущенные варианты, например %s, %q или обычный %v, могут дать неожиданный или неполный результат. Форматтеру следует определить разумное базовое поведение для неподдерживаемых сочетаний и не полагаться на то, что Error() будет автоматически вызван вместо него.