Добавление метода Unwrap к уже публичному типу ошибки: какое наблюдаемое изменение это может внести в поведение вызывающего кода?
Добавление Unwrap может сделать внутреннюю причину ошибки доступной для errors.Is и errors.As. Поэтому проверки, которые раньше возвращали false или не находили тип, начнут успешно проходить; внутренний тип ошибки фактически станет частью наблюдаемого поведения API.
До стандартизации wrapping в Go вызывающий код часто мог различать ошибки только по тексту или по специальным API конкретной библиотеки. Механизм Unwrap, errors.Is и errors.As позволил добавлять контекст, сохраняя программный доступ к исходной причине.
Это улучшило диагностику, но связало структуру цепочки ошибок с поведением публичного API: изменения wrapping теперь могут менять результаты проверок клиентского кода.
Предположим, библиотека раньше возвращала собственный тип ошибки без метода Unwrap. Вызывающий код не мог обнаружить скрытую причину через errors.Is или errors.As.
После добавления Unwrap та же ошибка начинает раскрывать причину. Это может быть полезно, но одновременно раскрывает наружу конкретный sentinel или тип ошибки, провоцирует зависимость от внутренней реализации и может изменить ветвление уже существующих клиентов.
errors.Is при обходе ошибки проверяет саму ошибку, а затем переходит к причине, возвращённой методом Unwrap() error. Если тип реализует Unwrap() []error, обход выполняется по нескольким причинам.
Следовательно, добавление Unwrap меняет не только внутреннюю реализацию. Например, проверка errors.Is(err, ErrQuota) могла быть ложной до изменения и стать истинной после него. Аналогично errors.As может начать находить тип, который раньше был недоступен.
При проектировании публичного API нужно добавлять Unwrap, если раскрытие причины является намеренным контрактом. Если библиотека хочет сохранить свободу менять внутренние ошибки, не следует без необходимости предоставлять доступ к конкретной причине; вместо этого можно определить стабильное поведение через собственный тип или метод Is.
Важно, что удаление уже опубликованного Unwrap также является потенциально несовместимым изменением: клиентские проверки перестанут находить ранее доступные причины. Поэтому наличие и семантика wrapping должны рассматриваться как часть контракта, а не только как деталь реализации.
Без Unwrap значение ErrQuota оставалось бы скрытым, и такая проверка вернула бы false. Сам текст Error() здесь не влияет на результат.
Публичный HTTP-клиент возвращал тип requestError, внутри которого хранилась ошибка транспорта. Изначально транспортная ошибка была полностью внутренней, поэтому библиотека могла менять реализацию без обещаний клиенту.
Вариант с добавлением Unwrap позволил пользователям проверять сетевые причины через errors.Is, но сделал конкретные транспортные sentinel-ошибки частью наблюдаемого поведения. Плюс — удобная программная обработка; минус — более жёсткая совместимость и риск утечки внутренних деталей.
Вариант без Unwrap сохранял инкапсуляцию, но вынуждал клиентов использовать отдельный стабильный метод, например проверку категории ошибки. Для публичной библиотеки выбран такой вариант: наружу предоставили собственную категорию, а внутреннюю транспортную ошибку оставили недоступной. Это позволило менять транспортный стек без изменения контракта клиентов.
Unwrap считается безопасным расширением API?Нет. Хотя оно обычно не ломает компиляцию, оно может изменить результаты errors.Is и errors.As, ветвление программ, метрики и HTTP-ответы. Это поведенческое изменение, поэтому его нужно оценивать как изменение публичного контракта.
Unwrap?Нет, автоматически раскрывается только причина, возвращаемая Unwrap. Но если причина имеет экспортируемый тип или известный sentinel, вызывающий код может начать зависеть от него. Поэтому важно возвращать только ту причину, которую библиотека готова поддерживать.
Unwrap методом Is, чтобы сохранить совместимость?Иногда да. Метод Is позволяет объявить стабильное соответствие категории без раскрытия конкретной внутренней ошибки. Однако он должен описывать намеренный контракт и не обязан предоставлять полный доступ к причине; если клиенту нужны данные конкретного типа, потребуется Unwrap или другой публичный API.