errors.Is() y errors.As()
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 56 de 107.
Cuando los errores se envuelven, las comprobaciones de igualdad simples como err == ErrNotFound ya no funcionan. El paquete errors proporciona dos funciones para inspeccionar cadenas de errores envueltos: errors.Is() y errors.As().
errors.Is() comprueba si algún error en la cadena coincide con un error centinela específico:
import "errors"
var ErrNotFound = errors.New("not found")
func FindUser(id int) error {
return fmt.Errorf("database lookup failed: %w", ErrNotFound)
}
err := FindUser(42)
if errors.Is(err, ErrNotFound) {
fmt.Println("User doesn't exist")
}Aunque el error devuelto está envuelto, errors.Is() desenvuelve la cadena y encuentra ErrNotFound en su interior.
errors.As() comprueba si algún error en la cadena coincide con un tipo específico y lo extrae:
type ValidationError struct {
Field string
}
func (e *ValidationError) Error() string {
return "validation failed: " + e.Field
}
err := fmt.Errorf("request failed: %w", &ValidationError{Field: "email"})
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Println("Invalid field:", ve.Field)
}Usa errors.Is() al comprobar errores centinela, y errors.As() cuando necesites acceder a campos de un tipo de error personalizado. Ambas funciones recorren toda la cadena de errores, lo que las hace esenciales para manejar errores envueltos adecuadamente.
Desafío
FácilVamos a construir un sistema de procesamiento de archivos que demuestre cómo inspeccionar adecuadamente las cadenas de errores envueltos utilizando errors.Is() y errors.As(). Crearás errores que se envuelven a medida que pasan por diferentes capas, y luego usarás estas funciones para identificar y extraer información de ellos.
Organizarás tu código en dos archivos:
errors.go: Define la infraestructura de errores para tu sistema de archivos.Crea un error centinela (sentinel error):
ErrPermissionDeniedcon el mensaje"permission denied"
Crea un tipo de error personalizado:
- Estructura
FileErrorcon los camposFilename(string) yOperation(string) - Su método
Error()debe devolver:[Operation] failed on file: [Filename]
Implementa dos funciones que simulen operaciones de archivos y envuelvan errores:
ReadFile(filename string) error- Si el nombre del archivo es"secret.txt", devuelveErrPermissionDenied. Si el nombre del archivo es"missing.txt", devuelve un*FileErrorcon Operation"read"y el Filename dado. De lo contrario, devuelvenil.ProcessFile(filename string) error- Llama aReadFile. Si se devuelve un error, envuélvelo usandofmt.Errorfcon el formato"processing failed: %w". De lo contrario, devuelvenil.
main.go: Lee un nombre de archivo de la entrada y llama aProcessFile. Usaerrors.Is()yerrors.As()para inspeccionar la cadena de errores envueltos e imprimir los mensajes apropiados según lo que encuentres dentro.
Se proporcionará la siguiente entrada:
- Línea 1: Nombre del archivo a procesar
Maneja el resultado inspeccionando la cadena de errores:
- Si
errors.Is()encuentraErrPermissionDenieden la cadena: imprime el mensaje de error completo, luego en una nueva línea imprimeAccess denied - check file permissions - Si
errors.As()encuentra un*FileErroren la cadena: imprime el mensaje de error completo, luego en una nueva línea imprimeFile issue: [Filename] during [Operation] - Si no ocurre ningún error: imprime
File '[filename]' processed successfully
Por ejemplo, dado secret.txt, tu salida debería ser:
processing failed: permission denied
Access denied - check file permissionsY dado missing.txt, tu salida debería ser:
processing failed: read failed on file: missing.txt
File issue: missing.txt during readY dado data.txt, tu salida debería ser:
File 'data.txt' processed successfullyObserva cómo, aunque los errores son envueltos por ProcessFile, aún puedes detectar el error centinela original con errors.Is() y extraer el tipo de error personalizado con errors.As(). Estas funciones recorren toda la cadena de errores para encontrar lo que estás buscando.
Hoja de referencia
Cuando los errores están envueltos (wrapped), utiliza errors.Is() y errors.As() del paquete errors para inspeccionar la cadena de errores.
errors.Is() comprueba si algún error en la cadena coincide con un error centinela (sentinel error):
import "errors"
var ErrNotFound = errors.New("not found")
err := fmt.Errorf("database lookup failed: %w", ErrNotFound)
if errors.Is(err, ErrNotFound) {
// ErrNotFound fue encontrado en la cadena
}errors.As() comprueba si algún error en la cadena coincide con un tipo específico y lo extrae:
type ValidationError struct {
Field string
}
func (e *ValidationError) Error() string {
return "validation failed: " + e.Field
}
err := fmt.Errorf("request failed: %w", &ValidationError{Field: "email"})
var ve *ValidationError
if errors.As(err, &ve) {
// ve ahora contiene el ValidationError de la cadena
fmt.Println(ve.Field)
}Utiliza errors.Is() para errores centinela y errors.As() cuando necesites acceder a campos de tipos de error personalizados. Ambas funciones recorren toda la cadena de errores.
Pruébalo tú mismo
package main
import (
"errors"
"fmt"
)
func main() {
// Leer el nombre del archivo desde la entrada
var filename string
fmt.Scanln(&filename)
// Llamar a ProcessFile con el nombre del archivo
err := ProcessFile(filename)
// TODO: Manejar el resultado inspeccionando la cadena de errores
//
// Usar errors.Is() para comprobar si ErrPermissionDenied está en la cadena:
// - Si se encuentra: imprimir el error completo, luego "Access denied - check file permissions"
//
// Usar errors.As() para comprobar si un *FileError está en la cadena:
// - Si se encuentra: imprimir el error completo, luego "File issue: [Filename] during [Operation]"
//
// Si no hay error: imprimir "File '[filename]' processed successfully"
_ = errors.Is // pista: usar errors.Is()
_ = errors.As // pista: usar errors.As()
_ = err
}
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