Errores centinela
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 55 de 107.
Un error centinela es una variable de error predefinida a nivel de paquete que representa una condición de error específica. A diferencia de crear nuevos errores cada vez con errors.New, los errores centinela se declaran una vez y se reutilizan en todo el código.
Los errores centinela se definen como variables a nivel de paquete, normalmente con nombres que empiezan por Err:
package user
import "errors"
var (
ErrNotFound = errors.New("user not found")
ErrInvalidEmail = errors.New("invalid email address")
ErrDuplicate = errors.New("user already exists")
)Las funciones devuelven estos errores predefinidos cuando ocurren condiciones específicas:
func FindUser(id int) (*User, error) {
user, exists := users[id]
if !exists {
return nil, ErrNotFound
}
return user, nil
}El poder de los errores centinela es que quienes realizan las llamadas pueden verificar condiciones de error específicas comparando directamente los valores de error:
user, err := FindUser(42)
if err == ErrNotFound {
fmt.Println("Creating new user...")
} else if err != nil {
fmt.Println("Unexpected error:", err)
}La biblioteca estándar de Go utiliza errores centinela extensamente. Por ejemplo, io.EOF señala el fin de la entrada, y sql.ErrNoRows indica un resultado de consulta vacío. Este patrón crea un contrato claro entre los paquetes y sus usuarios sobre qué errores esperar.
Sin embargo, las comprobaciones de igualdad simples fallan cuando los errores están envueltos. En la siguiente lección, aprenderás cómo errors.Is() resuelve este problema comprobando toda la cadena de errores.
Desafío
FácilVamos a construir un sistema de gestión de inventario que utiliza errores centinela (sentinel errors) para comunicar condiciones de fallo específicas. Definirás variables de error a nivel de paquete que los llamadores pueden verificar para manejar diferentes escenarios de manera adecuada.
Organizarás tu código en dos archivos:
inventory.go: Crea un sistema de inventario con errores centinela predefinidos para casos de fallo comunes.Define tres errores centinela a nivel de paquete:
ErrItemNotFoundcon el mensaje"item not found in inventory"ErrOutOfStockcon el mensaje"item is out of stock"ErrInsufficientQuantitycon el mensaje"insufficient quantity available"
Crea un struct
Inventoryque contenga un mapa de nombres de artículos y sus cantidades. Implementa estos métodos:NewInventory() *Inventory- crea un inventario vacíoAddItem(name string, quantity int)- añade o actualiza la cantidad de un artículoGetQuantity(name string) (int, error)- devuelve la cantidad de un artículo, oErrItemNotFoundsi no existePurchase(name string, quantity int) error- intenta comprar artículos. DevuelveErrItemNotFoundsi el artículo no existe,ErrOutOfStocksi la cantidad actual es cero, oErrInsufficientQuantitysi la cantidad solicitada excede el stock disponible. En caso de éxito, reduce el inventario y devuelvenil.
main.go: Lee las operaciones de inventario de la entrada y demuestra cómo los llamadores pueden verificar errores centinela específicos utilizando la comparación directa de igualdad.Lee el nombre del artículo y la cantidad a comprar de la entrada. Crea un inventario, añade el artículo
"laptop"con una cantidad de5, luego intenta la compra. Basándote en el error devuelto, imprime un mensaje específico que demuestre que identificaste qué error centinela ocurrió.
Se proporcionarán las siguientes entradas:
- Línea 1: Nombre del artículo a comprar
- Línea 2: Cantidad a comprar
Maneja el resultado verificando qué error específico fue devuelto:
- Si
err == ErrItemNotFound: imprimeError: Item '[name]' does not exist in our inventory - Si
err == ErrOutOfStock: imprimeError: Item '[name]' is currently out of stock - Si
err == ErrInsufficientQuantity: imprimeError: Cannot purchase [quantity] units of '[name]' - not enough in stock - Si tiene éxito: imprime
Successfully purchased [quantity] units of '[name]'
Por ejemplo, dado laptop y 3, tu salida debería ser:
Successfully purchased 3 units of 'laptop'Y dado phone y 2, tu salida debería ser:
Error: Item 'phone' does not exist in our inventoryY dado laptop y 10, tu salida debería ser:
Error: Cannot purchase 10 units of 'laptop' - not enough in stockHoja de referencia
Un error centinela (sentinel error) es una variable de error predefinida a nivel de paquete que representa una condición de error específica. Los errores centinela se declaran una vez y se reutilizan en todo el código.
Define los errores centinela como variables a nivel de paquete, típicamente con nombres que comienzan con Err:
package user
import "errors"
var (
ErrNotFound = errors.New("user not found")
ErrInvalidEmail = errors.New("invalid email address")
ErrDuplicate = errors.New("user already exists")
)Las funciones devuelven estos errores predefinidos cuando ocurren condiciones específicas:
func FindUser(id int) (*User, error) {
user, exists := users[id]
if !exists {
return nil, ErrNotFound
}
return user, nil
}Quienes llaman a la función pueden verificar condiciones de error específicas comparando los valores de error directamente:
user, err := FindUser(42)
if err == ErrNotFound {
fmt.Println("Creating new user...")
} else if err != nil {
fmt.Println("Unexpected error:", err)
}La biblioteca estándar de Go utiliza errores centinela de forma extensiva, como io.EOF y sql.ErrNoRows.
Pruébalo tú mismo
package main
import (
"fmt"
)
func main() {
// Leer entrada
var itemName string
var quantity int
fmt.Scanln(&itemName)
fmt.Scanln(&quantity)
// Crear inventario y añadir "laptop" con cantidad 5
inv := NewInventory()
inv.AddItem("laptop", 5)
// TODO: Intentar comprar el artículo
// Llamar a inv.Purchase(itemName, quantity)
// TODO: Comprobar qué error centinela específico se devolvió utilizando una comparación de igualdad directa
// Usar sentencias if/else if para comprobar:
// - err == ErrItemNotFound
// - err == ErrOutOfStock
// - err == ErrInsufficientQuantity
// - err == nil (éxito)
// TODO: Imprimir el mensaje apropiado basado en el error
// Si ErrItemNotFound: fmt.Printf("Error: Item '%s' does not exist in our inventory\n", itemName)
// Si ErrOutOfStock: fmt.Printf("Error: Item '%s' is currently out of stock\n", itemName)
// Si ErrInsufficientQuantity: fmt.Printf("Error: Cannot purchase %d units of '%s' - not enough in stock\n", quantity, itemName)
// Si tiene éxito: fmt.Printf("Successfully purchased %d units of '%s'\n", quantity, itemName)
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker Pool