Lecciones del módulo (2/5)
Gestión idiomática de errores
El manejo de errores en Go es explícito: sin excepciones, sin intento/captura. Cada función que puede fallar devuelve error como último valor y la persona que llama decide. Este capítulo recopila los patrones idiomáticos para producir, propagar e inspeccionar errores de manera sólida.
La regla de oro
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("config %s: %w", path, err)
}Cuatro principios:
errores el último retorno.- Verifique inmediatamente con
if err != nil. - Agregue contexto antes de propagar (con
fmt.Errorf+%w). - Nunca ignore un error: trátelo, regístrelo o devuélvalo.
_ = file.Close() // EXPLICITLY ignored (you know what you're doing)Errores centinela
Un error centinela es una variable global exportada var ErrXxx = errors.New("...") con la que la persona que llama puede comparar:
package store
var ErrNotFound = errors.New("not found")
func Get(key string) (string, error) {
if !exists(key) {
return "", ErrNotFound
}
// ...
}La persona que llama usa errors.Is (NO ==), que recorre la cadena de envoltura:
v, err := store.Get(k)
if errors.Is(err, store.ErrNotFound) {
// handle 404
}Ejemplos estándar: io.EOF, sql.ErrNoRows, os.ErrNotExist.
Errores escritos personalizados
Para transportar datos estructurados (código HTTP, campo no válido, etc.), defina un tipo:
type ValidationError struct {
Field string
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}La persona que llama usa errors.As para abatir de forma segura a lo largo de la cadena:
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Println("invalid field:", ve.Field)
}errors.As recorre la cadena Unwrap() hasta que encuentra un error asignable al puntero pasado.
Envolviendo con %w
fmt.Errorf con el verbo %w produce un error que envuelve el original, conservándolo para errors.Is/errors.As:
if err := db.Query(); err != nil {
return fmt.Errorf("load users: %w", err)
}Sin %w (usando %v o %s) obtienes una cadena más rica pero pierdes la cadena: la persona que llama ya no puede reconocer errores específicos.
Reglas para %w:
- Sólo uno por llamada
fmt.Errorf(el multi-wrap requiereerrors.Join). - El argumento DEBE ser un
error, no una cadena. - Añade contexto, no duplique el mensaje original.
// bad: duplicates
return fmt.Errorf("error: %w", err) // "error: file not found"
// good: useful context
return fmt.Errorf("load config %s: %w", path, err)errors.Join para múltiples errores
Desde Go 1.20, errors.Join(errs...) combina múltiples errores en uno que los mantiene todos detectables por errors.Is/As:
var errs []error
for _, item := range items {
if err := process(item); err != nil {
errs = append(errs, fmt.Errorf("item %s: %w", item.ID, err))
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}Cuándo usar panic
Nunca por errores esperados. Sólo en tres casos:
- Error irrecuperable: una invariante violada en un lugar que "no puede suceder" (
panic("unreachable")). - Inicialización fallida en
init()/main()de un programa independiente (falta configuración crítica → falla rápidamente). - API de biblioteca que lo documenta explícitamente: por ejemplo,
regexp.MustCompile(entra en pánico si el patrón no es válido; aceptable porque el patrón es una constante de tiempo de compilación).
Todo lo demás es error. No utilice panic como atajo para propagarse: rompe la composición, omite defer Close() y abruma a la persona que llama.
Ejercicios
Wrappa el error de os.ReadFile con fmt.Errorf usando el verbo %w y agregando el contesto 'config:' (con la ruta).
Solución disponible después de 3 intentos
Usa errores. Es para verificar si se ha errado en la cadena de envoltura corrisponde alla sentinella ErrNotFound y stampa 'manca!' en tal caso.
Solución disponible después de 3 intentos
¿Cuándo è idiomatico usare pánico en una librería Go?
func F(x int) {
// ??? panic(...)
}