Извлечение исходной ошибки с помощью `errors.Is`
Часть раздела Логика и управление потоком путешествия по GO на Coddy. Урок 40 из 68.
При работе с обёрнутыми ошибками нужно иметь возможность проверить, есть ли конкретная ошибка где-либо в цепочке ошибок. Функция errors.Is предоставляет именно такую возможность, позволяя распознавать ошибки-сентинелы, даже если они были обёрнуты несколько раз.
Функция errors.Is принимает два параметра: ошибку, которую вы хотите изучить, и целевую ошибку, которую ищете. Она возвращает true, если целевая ошибка найдена где-либо в цепочке ошибок:
var ErrNotFound = errors.New("not found")
wrappedErr := fmt.Errorf("failed to load user: %w", ErrNotFound)
if errors.Is(wrappedErr, ErrNotFound) {
fmt.Println("This is a not found error")
}Это современный рекомендуемый способ проверки конкретных ошибок в Go. В отличие от сравнения строк или прямой проверки на равенство, errors.Is корректно работает с обёрнутыми ошибками и проходит по цепочке ошибок, чтобы найти совпадения. Благодаря этому обработка ошибок остаётся надёжной и устойчивой, даже когда ошибки проходят через несколько уровней приложения.
Задание
ЛегкоСоздайте систему мониторинга подключений к базе данных, которая использует errors.Is для обнаружения определённых ошибок подключения в цепочках обёрнутых ошибок. В этом задании показано, как использовать errors.Is для обнаружения ошибок-маркеров, даже если они были обёрнуты дополнительным контекстом на нескольких уровнях приложения.
Вам будут переданы два входных значения:
- Строка со сведениями об операции с базой данных в формате
"database_name,operation,error_type"(например,"users_db,connect,connection_timeout") - Строка с контекстом приложения в формате
"service_name,retry_count"(например,"AuthService,3")
Ваша задача:
- Определить три ошибки-маркера с помощью
errors.New():ErrConnectionTimeoutс сообщением"connection timeout"ErrConnectionRefusedс сообщением"connection refused"ErrDatabaseNotFoundс сообщением"database not found"
- Разобрать первое входное значение, разделив его по запятым, чтобы получить имя базы данных, операцию и тип ошибки
- Разобрать второе входное значение, разделив его по запятым, чтобы получить имя службы и количество повторных попыток
- Создать исходную ошибку на основе типа ошибки:
- Если тип ошибки —
"connection_timeout": использоватьErrConnectionTimeout - Если тип ошибки —
"connection_refused": использоватьErrConnectionRefused - Если тип ошибки —
"database_not_found": использоватьErrDatabaseNotFound - Для любого другого типа ошибки: использовать
errors.New("unknown database error")
- Если тип ошибки —
- Создать ошибку, обёрнутую на уровне базы данных, с помощью
fmt.Errorfи%w:"failed to [operation] database [database_name]: %w" - Создать ошибку, обёрнутую на уровне службы:
"[service_name] database operation failed after [retry_count] retries: %w" - Использовать
errors.Is, чтобы проверить, соответствует ли итоговая обёрнутая ошибка каждой ошибке-маркеру, и вывести результаты:"Checking for connection timeout: [true/false]""Checking for connection refused: [true/false]""Checking for database not found: [true/false]"
- Вывести цепочку ошибок:
"Error Chain:""Original: [original_error_message]""Database level: [database_level_error_message]""Service level: [service_level_error_message]"
- Вывести анализ ошибки:
"Error Analysis:""Database: [database_name]""Operation: [operation]""Service: [service_name]""Retries: [retry_count]""Error type detected: [detected_error_type]", где detected_error_type — одно из значений "Connection Timeout", "Connection Refused", "Database Not Found" или "Unknown Error"
Используйте пакет strings, чтобы разделить входные строки по запятым, пакет errors для ошибок-маркеров и errors.Is, а пакет fmt — для обёртывания ошибок. В этом задании показано, как errors.Is обеспечивает надёжное обнаружение ошибок, которое корректно работает с обёрнутыми ошибками, что делает его необходимым для надёжной обработки ошибок в сложных приложениях.
Попробуйте сами
package main
import (
"errors"
"fmt"
"strings"
)
func main() {
// Считайте входные данные
var dbOperation string
var appContext string
fmt.Scanln(&dbOperation)
fmt.Scanln(&appContext)
// Разберите входные данные
dbParts := strings.Split(dbOperation, ",")
databaseName := dbParts[0]
operation := dbParts[1]
errorType := dbParts[2]
contextParts := strings.Split(appContext, ",")
serviceName := contextParts[0]
retryCount := contextParts[1]
// TODO: Напишите свой код ниже
// 1. Определите три сигнальные ошибки с помощью errors.New()
// 2. Создайте исходную ошибку в зависимости от типа ошибки
// 3. Создайте обёрнутую ошибку уровня базы данных с помощью fmt.Errorf и %w
// 4. Создайте обёрнутую ошибку уровня сервиса
// 5. Используйте errors.Is для проверки каждой сигнальной ошибки
// 6. Выведите результаты и цепочку ошибок
}В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Логика и управление потоком
1Продвинутое управление потоком
Оператор switch с `fallthrough`Выход из вложенных цикловПереход к следующей итерации конкретного циклаОператор `goto`Повторение: расширенное управление циклами4Проект: простой список задач
Настройка проектаДобавление задачи7Обработка ошибок: углублённый разбор
Пользовательские типы ошибокОбёртывание ошибок с помощью `%w`Извлечение исходной ошибки с помощью `errors.Is`Извлечение исходной ошибки с помощью `errors.As`Разбираемся с `panic`Использование `recover`Итоги — безопасное деление2Структуры и методы
Определение методов для структурПолучатели-значенияПолучатели-указателиВыбор получателейМетоды и функцииПовторение: поведение структур5Карты: подробный разбор
Карты структурУказатели как значения картПроверка карт на nilСравнение картПовторение — счётчик частоты слов8Проект: простой складской учёт
Обзор проектаПроверка запасов3Интерфейсы (основы)
Что такое интерфейс?Определение интерфейсаРеализация интерфейсаИспользование типов интерфейсовПустой интерфейсУтверждения типовПереключатель типовПовторение — формы и поведениеПотренируйтесь самостоятельно: Онлайн-компилятор Go