recover-go-recuperar-panic

Recover en Go: capturar panic con defer y sin abusos

  • 3 min

recover en Go es una función especial que permite capturar un panic dentro de un defer y recuperar el control de la goroutine.

En el artículo anterior vimos que panic no es el mecanismo normal para gestionar errores. recover tampoco convierte a Go en un lenguaje de try-catch. Es más bien una red de seguridad para fronteras muy concretas del programa.

Si algo entra en pánico, podemos evitar que el proceso completo termine, registrar el problema y responder de forma controlada. La recuperación debe hacerse en la misma goroutine.

recover solo funciona dentro de defer

recover captura un panic cuando la función diferida que se está ejecutando lo llama directamente.

func main() {
    defer func() {
        err := recover()
        if err != nil {
            fmt.Println("Recuperado:", err)
        }
    }()

    panic("algo fue muy mal")
}
Copied!

Salida:

Recuperado: algo fue muy mal
Copied!

Si llamamos a recover() fuera de un defer, simplemente devuelve nil. No hace nada especial.

Qué devuelve recover

panic puede recibir cualquier valor:

panic("mensaje")
panic(123)
panic(errors.New("error crítico"))
Copied!

Por eso recover devuelve any.

defer func() {
    err := recover()
    if err != nil {
        fmt.Printf("tipo: %T, valor: %v\n", err, err)
    }
}()
Copied!

En código real, lo normal es convertir ese valor a un mensaje de log o a un error interno.

Patrón típico en servidores

Un caso habitual para recover es proteger cada petición HTTP.

func recoverMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("panic: %v", err)
                http.Error(w, "Error interno", http.StatusInternalServerError)
            }
        }()

        next.ServeHTTP(w, r)
    })
}
Copied!

Así, si un handler tiene un bug, el middleware puede intentar devolver un 500 y el servidor seguirá atendiendo otras peticiones. Si la respuesta ya había enviado sus cabeceras, ya no podremos cambiar su código de estado.

El servidor de net/http ya recupera los pánicos de ServeHTTP, registra una traza y cierra la conexión o reinicia el stream HTTP/2. Un middleware propio sigue siendo útil si queremos controlar la respuesta y el registro.

Esto no significa que el bug esté arreglado. Solo hemos evitado que una petición rompa todo el proceso. Hay que registrar el error y corregir la causa.

No usar recover como try-catch

Esto es mala idea:

func leerConfig() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("algo falló")
        }
    }()

    // Código normal de negocio
}
Copied!

Si una función puede fallar de forma esperable, debe devolver error.

func leerConfig(ruta string) ([]byte, error) {
    datos, err := os.ReadFile(ruta)
    if err != nil {
        return nil, err
    }

    return datos, nil
}
Copied!

Los errores esperables van con error. Los panic son para situaciones excepcionales, bugs o estados irrecuperables.

recover y las goroutines

Un detalle importante: recover solo captura pánicos dentro de la misma goroutine.

func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("recuperado")
        }
    }()

    go func() {
        panic("fallo en otra goroutine")
    }()

    time.Sleep(time.Second)
}
Copied!

Ese recover del main no captura el panic de la goroutine. Si queréis proteger una goroutine, el defer debe estar dentro de esa goroutine.

go func() {
    defer func() {
        if err := recover(); err != nil {
            log.Println("panic en worker:", err)
        }
    }()

    trabajar()
}()
Copied!

Cuándo tiene sentido

recover suele tener sentido en límites del sistema:

  • Servidores HTTP, para aislar una petición rota.
  • Workers, para registrar un fallo y no perder el proceso entero.
  • Librerías que ejecutan callbacks del usuario.

Fuera de esos casos, normalmente es mejor devolver error.