errors.Is() и errors.As()
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 56 из 107.
Когда ошибки оборачиваются, простые проверки на равенство, такие как err == ErrNotFound, больше не работают. Пакет errors предоставляет две функции для проверки цепочек обернутых ошибок: errors.Is() и errors.As().
errors.Is() проверяет, соответствует ли какая-либо ошибка в цепочке определенной ошибке-индикатору (sentinel error):
import "errors"
var ErrNotFound = errors.New("not found")
func FindUser(id int) error {
return fmt.Errorf("database lookup failed: %w", ErrNotFound)
}
err := FindUser(42)
if errors.Is(err, ErrNotFound) {
fmt.Println("User doesn't exist")
}Несмотря на то, что возвращаемая ошибка обернута, errors.Is() разворачивает цепочку и находит ErrNotFound внутри.
errors.As() проверяет, соответствует ли какая-либо ошибка в цепочке определенному типу, и извлекает её:
type ValidationError struct {
Field string
}
func (e *ValidationError) Error() string {
return "validation failed: " + e.Field
}
err := fmt.Errorf("request failed: %w", &ValidationError{Field: "email"})
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Println("Invalid field:", ve.Field)
}Используйте errors.Is() при проверке на наличие сигнальных ошибок (sentinel errors) и errors.As(), когда вам нужно получить доступ к полям пользовательского типа ошибки. Обе функции обходят всю цепочку ошибок, что делает их незаменимыми для правильной обработки обернутых ошибок.
Задание
ЛегкоДавайте создадим систему обработки файлов, которая демонстрирует, как правильно проверять цепочки обернутых ошибок с помощью errors.Is() и errors.As(). Вы создадите ошибки, которые будут оборачиваться при прохождении через различные уровни, а затем будете использовать эти функции для идентификации и извлечения информации из них.
Вы организуете свой код в двух файлах:
errors.go: Определите инфраструктуру ошибок для вашей файловой системы.Создайте сигнальную ошибку (sentinel error):
ErrPermissionDeniedс сообщением"permission denied"
Создайте пользовательский тип ошибки:
- Структура
FileErrorс полямиFilename(string) иOperation(string) - Ее метод
Error()должен возвращать:[Operation] failed on file: [Filename]
Реализуйте две функции, которые имитируют операции с файлами и оборачивают ошибки:
ReadFile(filename string) error— если filename равен"secret.txt", вернитеErrPermissionDenied. Если filename равен"missing.txt", верните*FileErrorс Operation"read"и данным Filename. В противном случае вернитеnil.ProcessFile(filename string) error— вызываетReadFile. Если возвращается ошибка, оберните ее с помощьюfmt.Errorfс форматом"processing failed: %w". В противном случае вернитеnil.
main.go: Считайте имя файла из входных данных и вызовитеProcessFile. Используйтеerrors.Is()иerrors.As()для проверки цепочки обернутых ошибок и вывода соответствующих сообщений в зависимости от того, что вы найдете внутри.
Будут предоставлены следующие входные данные:
- Строка 1: Имя файла для обработки
Обработайте результат, проверяя цепочку ошибок:
- Если
errors.Is()находитErrPermissionDeniedв цепочке: выведите полное сообщение об ошибке, затем на новой строке выведитеAccess denied - check file permissions - Если
errors.As()находит*FileErrorв цепочке: выведите полное сообщение об ошибке, затем на новой строке выведитеFile issue: [Filename] during [Operation] - Если ошибка не возникла: выведите
File '[filename]' processed successfully
Например, для secret.txt ваш вывод должен быть таким:
processing failed: permission denied
Access denied - check file permissionsА для missing.txt ваш вывод должен быть таким:
processing failed: read failed on file: missing.txt
File issue: missing.txt during readА для data.txt ваш вывод должен быть таким:
File 'data.txt' processed successfullyОбратите внимание, что даже если ошибки обернуты функцией ProcessFile, вы все равно можете обнаружить исходную сигнальную ошибку с помощью errors.Is() и извлечь пользовательский тип ошибки с помощью errors.As(). Эти функции проходят по всей цепочке ошибок, чтобы найти то, что вы ищете.
Шпаргалка
Когда ошибки обернуты, используйте errors.Is() и errors.As() из пакета errors для проверки цепочки ошибок.
errors.Is() проверяет, совпадает ли какая-либо ошибка в цепочке с эталонной ошибкой (sentinel error):
import "errors"
var ErrNotFound = errors.New("not found")
err := fmt.Errorf("database lookup failed: %w", ErrNotFound)
if errors.Is(err, ErrNotFound) {
// ErrNotFound был найден в цепочке
}errors.As() проверяет, соответствует ли какая-либо ошибка в цепочке определенному типу, и извлекает ее:
type ValidationError struct {
Field string
}
func (e *ValidationError) Error() string {
return "validation failed: " + e.Field
}
err := fmt.Errorf("request failed: %w", &ValidationError{Field: "email"})
var ve *ValidationError
if errors.As(err, &ve) {
// ve теперь содержит ValidationError из цепочки
fmt.Println(ve.Field)
}Используйте errors.Is() для эталонных ошибок и errors.As(), когда вам нужно получить доступ к полям пользовательских типов ошибок. Обе функции проходят по всей цепочке ошибок.
Попробуйте сами
package main
import (
"errors"
"fmt"
)
func main() {
// Считать имя файла из ввода
var filename string
fmt.Scanln(&filename)
// Вызвать ProcessFile с именем файла
err := ProcessFile(filename)
// TODO: Обработать результат, проверив цепочку ошибок
//
// Используйте errors.Is(), чтобы проверить, есть ли ErrPermissionDenied в цепочке:
// - Если найдено: вывести полную ошибку, затем "Access denied - check file permissions"
//
// Используйте errors.As(), чтобы проверить, есть ли *FileError в цепочке:
// - Если найдено: вывести полную ошибку, затем "File issue: [Filename] during [Operation]"
//
// Если ошибки нет: вывести "File '[filename]' processed successfully"
_ = errors.Is // подсказка: используйте errors.Is()
_ = errors.As // подсказка: используйте errors.As()
_ = err
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool