В Go ошибка - это builtin-интерфейс с единственным методом Error() string
.
Собственно это все что нужно знать.
Можно создавать свои ошибки с контекстом (здесь я не имею ввиду context пакет) необходимой "толщины",
а можно воспользоваться готовой простой имплементацией в SDK - пакетом errors
.
Когда мы создаем свои ошибки, с помощью вызовов errors.New()
или fmt.Errorf()
, мы создаем обычную переменную.
Под капотом это просто указатель на структуру с единственным полем, строкой.
Как мы понимаем в клиентском коде, что произошла ошибка определенного типа? Ответ: сравнением переменной с самой собой. Если создать две ошибки с одинаковым текстом, то это будет два указателя на разные области памяти и их сравнение даст отрицательный результат.
Пакет errors предлагает единственную готовую exported ошибку: ErrUnsupported
.
Эту ошибку не рекомендуется возвращать напрямую, но обернув.
Возвращать эту ошибку нужно в случаях, если запрошенная процедура не поддерживается, например,
создание hard link в файловых системах, где это невозможно.
Слово "всегда" следует интерпретировать как "взять за правило об этом думать". Wrapping ошибок позволяет добавить дополнительную информацию, которую скорее стоит добавить. Ошибки происходят редко и лучше сопроводить каждую из них информативным контекстом (context пакет тут ни при чем). На каждом уровне стека вызова есть какой-то контекст, в котором возникла эта ошибка. Стоит рассмотреть возможность добавления этого контекста на каждом уровне. Тогда в вызывающем коде, когда ошибка получена и на руках у неё есть достаточно подробный след (трейс) при каких обстоятельствах эта ошибка возникла.
Так как я продолжительное время работал с Java и PHP, то мне знакома модель обработки ошибок через исключения. В этом модели по-хорошему нужно также добавлять контекст на каждом уровне стека вызова, но так делать не принято. Часто можно словить на высоком уровне ошибку из глубины и без контекста каждого уровня сложнее восстановить состояние, при котором ошибка возникла.
Если у нас ошибка с "богатым" контекстом, то создаем структуру и возвращаем указатель на эту структуру.
Для референса можно глянуть на *fs.PathError
и метод os.Open
, который
возвращает эту ошибку.
Используя сторонние ошибки структуры нужно в первую очередь обратить внимание на метод Error() string
:
если ресивер этого метода указатель, то и ошибка - это указатель на структуру.
fs.PathError
Если вы используете пакет errors для работы с ошибками, то возможно обратили внимание на рекомендацию использовать функции As и Is вместо сравнений/type assertions, но все же последние встречается чаще.
Ключевая проверка в методе As: reflectlite.TypeOf(err).AssignableTo(targetType)
,
т.е. проверяется можно ли присвоить второму аргументы значение типа первого аргумента.
Итак, As - разворачиваем до возможности присвоить.
Английский предлог As (в роли) используется для уточнения отношения.
Например, "She works as a software developer". Можно опустить "works": "She ... as a software developer".
Аналогичная конструкция применима и к ошибкам. Ошибка X в роли Y - это ок (As возвращает bool)?
Is-сравнение ошибок проще: сравниваются конкретные типы после проверки reflectlite.TypeOf(target).Comparable()
Самый простой способ:
Самый простой способ + немного контекста:
Много контекста:
Error() string
.Красиво отображает трейс. Может даже показать соседние строчки кода. Как это работает? По фреймам трейса определяет файл и строчку, читает в нем строчки рядом с проблемной строкой и отображает. Работать это может только в среде разработки. В production у меня остается только бинарник, да и некому будет смотреть на красивые ошибки. Вердикт: я скорее не буду им пользоваться.