Menu
Coddy logo textTech

Обертывание ошибок (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().

challenge icon

Задание

Легко

Давайте создадим систему обработки заказов, которая демонстрирует оборачивание ошибок (error wrapping) на нескольких уровнях. Вы создадите цепочку функций, где каждый уровень добавляет контекст к ошибкам, что позволяет легко отследить, где именно возникла проблема.

Вы организуете свой код в двух файлах:

  • orders.go: Создайте логику обработки заказов с несколькими уровнями, которые оборачивают ошибки по мере их передачи вверх.

    Реализуйте три функции, образующие цепочку обработки:

    • ValidateItem(itemID string) error — возвращает ошибку с сообщением "item not found", если itemID равен "INVALID", в противном случае возвращает nil
    • ProcessOrder(orderID, itemID string) error — вызывает ValidateItem. Если она возвращает ошибку, оберните ее с контекстом: "processing order [orderID]: %w". В противном случае верните nil
    • SubmitOrder(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
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование