Обертывание ошибок (fmt.Errorf)
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 54 из 107.
Когда ошибки проходят через несколько уровней вашего приложения, знание того, где возникла ошибка, становится критически важным. Функция Go fmt.Errorf с глаголом %w позволяет оборачивать ошибки, сохраняя исходную ошибку и добавляя контекст.
Обертывание ошибок создает цепочку ошибок. Каждый уровень может добавить информацию о том, что он пытался сделать, когда произошла ошибка:
func ReadConfig(filename string) error {
data, err := os.ReadFile(filename)
if err != nil {
return fmt.Errorf("reading config file: %w", err)
}
// обработка данных...
return nil
}Спецификатор %w особенный — он оборачивает исходную ошибку внутри новой. Это отличается от %v, который просто преобразует ошибку в строку и теряет идентичность исходной ошибки.
func LoadSettings() error {
err := ReadConfig("settings.json")
if err != nil {
return fmt.Errorf("loading settings: %w", err)
}
return nil
}Когда LoadSettings завершается с ошибкой, сообщение об ошибке показывает всю цепочку: "loading settings: reading config file: open settings.json: no such file or directory". Каждый уровень добавляет контекст, что значительно упрощает отладку.
Обернутая ошибка по-прежнему содержит внутри себя исходную ошибку. В следующем уроке вы узнаете, как разворачивать эти ошибки и проверять их содержимое с помощью errors.Is() и errors.As().
Задание
ЛегкоДавайте создадим систему обработки заказов, которая демонстрирует оборачивание ошибок (error wrapping) на нескольких уровнях. Вы создадите цепочку функций, где каждый уровень добавляет контекст к ошибкам, что позволяет легко отследить, где именно возникла проблема.
Вы организуете свой код в двух файлах:
orders.go: Создайте логику обработки заказов с несколькими уровнями, которые оборачивают ошибки по мере их передачи вверх.Реализуйте три функции, образующие цепочку обработки:
ValidateItem(itemID string) error— возвращает ошибку с сообщением"item not found", если itemID равен"INVALID", в противном случае возвращаетnilProcessOrder(orderID, itemID string) error— вызываетValidateItem. Если она возвращает ошибку, оберните ее с контекстом:"processing order [orderID]: %w". В противном случае вернитеnilSubmitOrder(customerName, orderID, itemID string) error— вызываетProcessOrder. Если она возвращает ошибку, оберните ее с контекстом:"submitting order for [customerName]: %w". В противном случае вернитеnil
Каждый уровень должен использовать
fmt.Errorfс глаголом%wдля оборачивания ошибки с нижележащего уровня, выстраивая цепочку контекста.main.go: Считайте детали заказа из входных данных, вызовитеSubmitOrderи выведите результат. Если возникла ошибка, выведите полную цепочку ошибок. В случае успеха выведите подтверждающее сообщение.
Будут предоставлены следующие входные данные:
- Строка 1: Имя клиента (Customer name)
- Строка 2: ID заказа (Order ID)
- Строка 3: ID товара (Item ID)
Выведите результат:
- Если произошла ошибка:
Error: [full error chain] - В случае успеха:
Order [orderID] submitted successfully for [customerName]
Например, для входных данных Alice, ORD-123 и INVALID ваш вывод должен быть:
Error: submitting order for Alice: processing order ORD-123: item not foundОбратите внимание, как сообщение об ошибке показывает полную цепочку — вы можете проследить проблему от попытки отправки на верхнем уровне через обработку заказа до фактического сбоя валидации. Каждый уровень добавил свой собственный контекст, используя %w.
А для Bob, ORD-456 и ITEM-001 ваш вывод должен быть:
Order ORD-456 submitted successfully for BobШпаргалка
Используйте fmt.Errorf со спецификатором %w для оборачивания ошибок с сохранением исходной ошибки:
func ReadConfig(filename string) error {
data, err := os.ReadFile(filename)
if err != nil {
return fmt.Errorf("reading config file: %w", err)
}
return nil
}Спецификатор %w оборачивает исходную ошибку внутри новой, в отличие от %v, который только преобразует ошибку в строку и теряет идентичность исходной ошибки.
Оборачивание ошибок создает цепочку, в которой каждый уровень добавляет контекст:
func LoadSettings() error {
err := ReadConfig("settings.json")
if err != nil {
return fmt.Errorf("loading settings: %w", err)
}
return nil
}Результирующее сообщение об ошибке показывает всю цепочку: "loading settings: reading config file: open settings.json: no such file or directory"
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Прочитать имя клиента
scanner.Scan()
customerName := scanner.Text()
// Прочитать ID заказа
scanner.Scan()
orderID := scanner.Text()
// Прочитать ID товара
scanner.Scan()
itemID := scanner.Text()
// TODO: Вызвать SubmitOrder с входными значениями
// TODO: Если возникла ошибка, вывести: Error: [full error chain]
// TODO: В случае успеха вывести: Order [orderID] submitted successfully for [customerName]
_ = customerName
_ = orderID
_ = itemID
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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