Menu
Coddy logo textTech

Sentinel-ошибки

Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 55 из 107.

Ошибка-индикатор (sentinel error) — это предопределенная переменная ошибки на уровне пакета, которая представляет конкретное состояние ошибки. В отличие от создания новых ошибок каждый раз с помощью errors.New, ошибки-индикаторы объявляются один раз и повторно используются во всем коде.

Вы определяете ошибки-индикаторы (sentinel errors) как переменные уровня пакета, обычно с именами, начинающимися с Err:

package user

import "errors"

var (
    ErrNotFound     = errors.New("user not found")
    ErrInvalidEmail = errors.New("invalid email address")
    ErrDuplicate    = errors.New("user already exists")
)

Функции возвращают эти предопределенные ошибки при возникновении определенных условий:

func FindUser(id int) (*User, error) {
    user, exists := users[id]
    if !exists {
        return nil, ErrNotFound
    }
    return user, nil
}

Сила сигнальных ошибок (sentinel errors) заключается в том, что вызывающие функции могут проверять конкретные условия ошибок, напрямую сравнивая значения ошибок:

user, err := FindUser(42)
if err == ErrNotFound {
    fmt.Println("Creating new user...")
} else if err != nil {
    fmt.Println("Unexpected error:", err)
}

Стандартная библиотека Go широко использует ошибки-индикаторы (sentinel errors). Например, io.EOF сигнализирует об окончании ввода, а sql.ErrNoRows указывает на пустой результат запроса. Этот паттерн создает четкий контракт между пакетами и их пользователями относительно того, какие ошибки следует ожидать.

Однако простые проверки на равенство перестают работать, когда ошибки обернуты. В следующем уроке вы узнаете, как errors.Is() решает эту проблему, проверяя всю цепочку ошибок.

challenge icon

Задание

Легко

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

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

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

    Определите три сигнальные ошибки на уровне пакета:

    • ErrItemNotFound с сообщением "item not found in inventory"
    • ErrOutOfStock с сообщением "item is out of stock"
    • ErrInsufficientQuantity с сообщением "insufficient quantity available"

    Создайте структуру Inventory, которая хранит карту (map) имен товаров и их количества. Реализуйте следующие методы:

    • NewInventory() *Inventory — создает пустой инвентарь
    • AddItem(name string, quantity int) — добавляет или обновляет количество товара
    • GetQuantity(name string) (int, error) — возвращает количество товара или ErrItemNotFound, если он не существует
    • Purchase(name string, quantity int) error — пытается приобрести товары. Возвращает ErrItemNotFound, если товар не существует, ErrOutOfStock, если текущее количество равно нулю, или ErrInsufficientQuantity, если запрошенное количество превышает имеющийся запас. В случае успеха уменьшите количество в инвентаре и верните nil.
  • main.go: Считайте операции инвентаризации из входных данных и продемонстрируйте, как вызывающая сторона может проверять наличие конкретных сигнальных ошибок с помощью прямого сравнения на равенство.

    Считайте название товара и количество для покупки из входных данных. Создайте инвентарь, добавьте товар "laptop" с количеством 5, затем попытайтесь совершить покупку. В зависимости от возвращенной ошибки выведите конкретное сообщение, показывающее, что вы определили, какая именно сигнальная ошибка произошла.

Будут предоставлены следующие входные данные:

  • Строка 1: Название товара для покупки
  • Строка 2: Количество для покупки

Обработайте результат, проверив, какая именно ошибка была возвращена:

  • Если err == ErrItemNotFound: выведите Error: Item '[name]' does not exist in our inventory
  • Если err == ErrOutOfStock: выведите Error: Item '[name]' is currently out of stock
  • Если err == ErrInsufficientQuantity: выведите Error: Cannot purchase [quantity] units of '[name]' - not enough in stock
  • В случае успеха: выведите Successfully purchased [quantity] units of '[name]'

Например, для laptop и 3 ваш вывод должен быть:

Successfully purchased 3 units of 'laptop'

А для phone и 2 ваш вывод должен быть:

Error: Item 'phone' does not exist in our inventory

И для laptop и 10 ваш вывод должен быть:

Error: Cannot purchase 10 units of 'laptop' - not enough in stock

Шпаргалка

Sentinel error (ошибка-индикатор) — это предопределенная переменная ошибки на уровне пакета, которая представляет конкретное условие ошибки. Sentinel-ошибки объявляются один раз и используются повторно во всем коде.

Определяйте sentinel-ошибки как переменные на уровне пакета, обычно с именами, начинающимися с Err:

package user

import "errors"

var (
    ErrNotFound     = errors.New("user not found")
    ErrInvalidEmail = errors.New("invalid email address")
    ErrDuplicate    = errors.New("user already exists")
)

Функции возвращают эти предопределенные ошибки при возникновении определенных условий:

func FindUser(id int) (*User, error) {
    user, exists := users[id]
    if !exists {
        return nil, ErrNotFound
    }
    return user, nil
}

Вызывающая сторона может проверять наличие конкретных условий ошибки, напрямую сравнивая значения ошибок:

user, err := FindUser(42)
if err == ErrNotFound {
    fmt.Println("Creating new user...")
} else if err != nil {
    fmt.Println("Unexpected error:", err)
}

Стандартная библиотека Go широко использует sentinel-ошибки, такие как io.EOF и sql.ErrNoRows.

Попробуйте сами

package main

import (
	"fmt"
)

func main() {
	// Чтение входных данных
	var itemName string
	var quantity int
	fmt.Scanln(&itemName)
	fmt.Scanln(&quantity)

	// Создание инвентаря и добавление "laptop" в количестве 5 штук
	inv := NewInventory()
	inv.AddItem("laptop", 5)

	// TODO: Попытаться приобрести товар
	// Вызов inv.Purchase(itemName, quantity)

	// TODO: Проверить, какая именно ошибка (sentinel error) была возвращена, используя прямое сравнение
	// Используйте операторы if/else if для проверки:
	// - err == ErrItemNotFound
	// - err == ErrOutOfStock
	// - err == ErrInsufficientQuantity
	// - err == nil (успех)

	// TODO: Вывести соответствующее сообщение в зависимости от ошибки
	// Если ErrItemNotFound: fmt.Printf("Error: Item '%s' does not exist in our inventory\n", itemName)
	// Если ErrOutOfStock: fmt.Printf("Error: Item '%s' is currently out of stock\n", itemName)
	// Если ErrInsufficientQuantity: fmt.Printf("Error: Cannot purchase %d units of '%s' - not enough in stock\n", quantity, itemName)
	// Если успех: fmt.Printf("Successfully purchased %d units of '%s'\n", quantity, itemName)
}
quiz iconПроверьте себя

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

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