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.
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