Golang: поведение при ошибках
В настоящее время мы используем Errors в go во многих частях кода. Ошибка является важным компонентом Go, а также других языков. Взгляните на интерфейс ошибок в Go:
type error interface {
Error() string
}
Все просто, правда? это дух Go! Теперь мы рассмотрим несколько сценариев, в которых нам нужно больше, чем строка «содержимого» ошибки:
- Ошибки базы данных: ошибка подключения, тайм-аут ввода-вывода, повторяющийся индекс…
- Ошибки обслуживания: ошибка с возможностью повторения и без возможности повторения.
- …
У нас может возникнуть наивный подход:
err := SomeFunc()
if err.Error() == "This is a DB connection error" {
// Handle DB connection error here
}
У этого подхода есть несколько проблем:
- Программистам необходимо «запомнить» строку ошибки, скопировать и вставить ее везде в коде всякий раз, когда устанавливается соединение с БД, поэтому его трудно поддерживать, трудно изменить.
- Сама строка ошибки длинная и занимает большую часть строк / символов кода в исходном коде, поэтому ее трудно читать.
- Не каждая ошибка указывается с постоянной строкой ошибки, они могут смешиваться с некоторыми кодами ошибок, подробностями о контексте, в котором они работают (например, ошибка содержит идентификатор пользователя, чтобы указать, что у пользователя недостаточно прав).
Есть более приемлемый подход, который, как мы видим, используется в исходном коде Go: предопределенный тип ошибки.
// ErrNoRows возвращается сканированием, когда queryrow не возвращает строку
// В таком случае строка запроса возвращает значение строки-заполнителя *, которое
// откладывает эту ошибку до сканирования.
var ErrNoRows = errors.New("sql: no rows in result set")
Каждая ошибка будет начинаться с кода “Err” и сопровождаться именем ошибки. Этот подход совершенно прост и удобен для чтения/понимания/использования. Но это просто обернет текст ошибки в более запоминающуюся переменную, это не могло решить проблему первоначального подхода.
Итак, не думайте о том, как представить тип ошибки в самой переменной ошибки, подумайте о ее значении или поведении: вместо определения типа ошибки соединения с БД, определяет поведение ошибки соединения с БД. Использование подхода ошибочного поведения дает следующие преимущества:
- Легко изменить: измените способ устранения ошибки. Поведение - это просто изменение «содержимого» метода, который формирует поведение, это не нарушает поведение, поэтому ничего не отличается от внешнего вида.
- Легко расширить: Добавить / удалить поведение при ошибке.
- Легко получить доступ к списку поддерживаемых ошибок: обратитесь к списку методов в интерфейсе ошибок, который покажет все поддерживаемые варианты поведения.
Чтобы реализовать поведение ошибки в Go, мы используем интерфейс, есть несколько способов добиться этого, поэтому давайте рассмотрим их и лучший (мое восприятие), который ясен, меньше кода и легко понять последний в список:
Реализуйте методы в интерфейсе ошибки
type NotFoundError interface{ NotFound() }
type serviceError struct {
err error
}
func (serviceError) NotFound() {}
func (e serviceError) Error() string { return e.err.Error() }
func NewServiceError(err error) error {
return serviceError{
err: err,
}
}
NotFoundError - это поведение ошибки, а целевая ошибка - serviceError реализует метод NotFound для удовлетворения поведения NotFoundError и реализует метод Error для удовлетворения интерфейса ошибки. Чтобы проверить, является ли ошибка NotFoundError, мы можем использовать два способа:
if _, isNotFound := err.(NotFoundError); isNotFound {}
или
if errors.As(err, new(NotFoundError)) {}
Я имею в виду второй способ.
Чтобы добавить в ошибку больше поведения, просто реализуйте методы, удовлетворяющие интерфейсу.
Составить поведение в структуре ошибки
type NotFoundError interface{ NotFound() }
type serviceError struct {
error
NotFoundError
}
func NewServiceError(err error) error {
return serviceError{
error: err,
}
}
Эта реализация более понятна, просто скомпонуйте поведение, которое вы хотите, в структуру ошибки, и ошибка будет иметь такое же поведение. Все просто, правда? Чтобы проверить ошибку, используйте способы, описанные выше.