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() решает эту проблему, проверяя всю цепочку ошибок.
Задание
ЛегкоДавайте создадим систему управления инвентарем, которая использует сигнальные ошибки (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)
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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