Как стать автором
Обновить

Проводим ревизию ошибок

Go *
Перевод
Автор оригинала: Dave Cheney

Часто используемый контракт для функций, которые возвращают значение ошибки интерфейсного типа, заключается в том, что вызывающая сторона не должна ничего знать заранее о состоянии других значений, возвращаемых этим вызовом, без предварительной проверки ошибки.


В большинстве случаев значения ошибок, возвращаемые функциями, должны быть непрозрачными для вызывающей стороны. То есть тест, в котором ошибка равна nil, показывает, был ли вызов успешным или неудачным, и это все, что нужно сделать.



картинка отсюда


Небольшое число случаев, обычно связанных с взаимодействием с внешним миром, например, сетевой активностью, требует, чтобы вызывающая сторона изучила природу ошибки, чтобы решить, целесообразно ли повторить операцию.


Обычное требование для авторов пакетов — возвращать ошибки известного открытого типа, чтобы вызывающий мог использовать проверку типа (type assert) и подробно изучить их. Я считаю, что эта практика приводит к ряду нежелательных результатов:


  • Открытые типы ошибок увеличивают "площадь контакта" с API пакета.
  • Новые реализации должны возвращать только типы, указанные в объявлении интерфейса, даже если они плохо подходят.
  • Тип ошибки, после добавления в код, не может быть изменен или объявлен устаревшим без нарушения совместимости, делая API хрупким.

Подтверждайте ожидаемое поведение, а не тип ошибок


Не утверждайте, что значение ошибки является конкретным типом, а скорее утверждайте, что значение реализует определенное поведение.


Это предложение соответствует природе неявных интерфейсов Go, а не является [подтипом] природы языков, основанных на наследовании. Рассмотрим этот пример:


func isTimeout(err error) bool {
        type timeout interface {
                Timeout() bool
        }
        te, ok := err.(timeout)
        return ok && te.Timeout()
}

Вызывающая сторона может использовать isTimeout (), чтобы определить, связана ли ошибка с тайм-аутом, посредством реализации интерфейса тайм-аута, а затем подтвердить, была ли ошибка связана с тайм-аутом — всё это, не зная ничего о типе или исходном источнике значения ошибки.


Этот метод дает возможность оборачивания ошибок, как правило, библиотеками, которые добавляют пояснения к пути ошибки; при условии, что обернутые типы ошибок также реализуют интерфейсы ошибок, которые они оборачивают.


Это может показаться неразрешимой проблемой, но на практике существует довольно мало общепринятых методов интерфейса, поэтому Timeout() bool и Temporary() bool будут охватывать большой набор вариантов использования.


В заключение


Подтверждайте ожидаемое поведение, а не тип ошибок


Для авторов пакетов


Если ваш пакет генерирует ошибки временного характера, убедитесь, что вы возвращаете типы ошибок, которые реализуют соответствующие методы интерфейса. Если вы оборачиваете значения ошибок на выходе, убедитесь, что ваши обертки поддерживают интерфейс(ы) с которым реализовано исходное значение ошибки.


Для пользователей пакета


Если вам нужно проверить ошибку, используйте интерфейсы для подтверждения ожидаемого поведения, а не типа ошибки. Не спрашивайте авторов пакетов об открытых типах ошибок; попросите их привести свои типы в соответствие с общими интерфейсами, указав методы Timeout() или Temporary() в зависимости от ситуации.


Об авторе


Автор данной статьи, Дейв Чини, является автором многих популярных пакетов для Go, например https://github.com/pkg/errors и https://github.com/davecheney/httpstat.


От переводчика


Хоть оригинальная статья датирована концом 2014 года, мне кажется, он не потеряла актуальности. Описанный подход встречается в немногих пакетах, тогда как остальные используют обычный подход для ошибок.


Правки в текст для повышения понятности материала приветствуются!

Теги:
Хабы:
Всего голосов 10: ↑9 и ↓1 +8
Просмотры 2.2K
Комментарии 2
Комментарии Комментарии 2

Истории

Работа

Go разработчик
113 вакансий