🐹 Aula 47 – Tutorial Golang – Uso do Recover no Controle de Fluxo
Introdução
No Go, o
panic indica um estado de erro crítico que interrompe a execução normal do programa. No entanto, às vezes pode ser desejável tentar “resgatar” (recuperar) o fluxo do programa após um
panic, permitindo uma finalização mais tranquila e controlada. E é aí que entra o
recover.
O
recover() é uma função
built-in em Go, capaz de capturar um
panic e evitar que o programa termine abruptamente.
Para funcionar, o
recover precisa ser chamado dentro de um
defer e na mesma função (ou em funções
chamadas pela função) onde o
panic pode ocorrer. Caso contrário, ele não consegue capturar o erro.
Na aula anterior, vimos o uso de
defer e como ele é executado ao final da função. Hoje, vamos explorar como combinar
defer +
recover para lidar com situações em que queremos retomar o controle após um
panic.
1. Exemplo Simples (Retirado do Go By Example)
Este exemplo é direto e ilustra o funcionamento do
recover com o uso de
panic.
46 - recover/basic_recover.go
package main
import "fmt"
func mayPanic() {
panic("a problem")
}
func main() {
defer func() {
// recover() só funciona dentro de uma função defirada
if r := recover(); r != nil {
fmt.Println("Recovered. Error:\n", r)
}
}()
mayPanic()
// Essa linha não será executada, pois a execução retoma no defer acima
fmt.Println("Depois de mayPanic()")
}
Como funciona:
mayPanic() dispara um panic simples.
- A função anônima (a que tem o defer) verifica se há um panic em andamento usando
recover().
- Se
recover() capturar um valor diferente de nil, significa que houve um panic, então o programa exibe a mensagem de erro e continua a execução.
Resultado esperado ao rodar:
Recovered. Error:
a problem
Note que a linha
fmt.Println("Depois de mayPanic()") não é executada porque o fluxo é retomado dentro do
defer e não volta para a linha seguinte de
mayPanic().
2. Exemplo Prático de Recover em Funções Aninhadas
Imagine que você tem um conjunto de funções em cadeia, e uma delas pode disparar um
panic. Podemos encapsular o
recover() em um local central para capturar qualquer falha que ocorra nas funções chamadas.
47 - recover/nested-functions/nested_recover.go
package main
import (
"fmt"
)
func operacaoCritica() {
// Simula uma condição de erro
panic("Erro crítico na operação!")
}
func funcaoIntermediaria() {
// Chama a função que pode gerar um panic
operacaoCritica()
}
func ExecutarOperacaoComSeguranca() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recuperado na função ExecutarOperacaoComSeguranca:", r)
}
}()
// Chama uma função intermediária que por sua vez chama operacaoCritica()
funcaoIntermediaria()
fmt.Println("Operação intermediária concluída com sucesso!")
}
func main() {
fmt.Println("Iniciando main...")
ExecutarOperacaoComSeguranca()
fmt.Println("Finalizando main!")
}
Explicação:
operacaoCritica() dispara um panic sempre que for chamada (simulando uma falha crítica).
funcaoIntermediaria() é apenas um “meio de campo” que chama a operação crítica.
ExecutarOperacaoComSeguranca() utiliza defer e recover() para capturar o panic gerado pela operacaoCritica().
- Ao rodar este código, você verá a mensagem de erro sendo capturada e o fluxo continua após o recover.
Saída esperada:
Iniciando main...
Recuperado na função ExecutarOperacaoComSeguranca: Erro crítico na operação!
Finalizando main!
Note que a linha
fmt.Println("Operação intermediária concluída com sucesso!") não será exibida, pois o fluxo é interrompido no
panic, e só é retomado dentro do
defer, não voltando para o ponto após
funcaoIntermediaria().
3. Exemplo com HTTP Server
Uma situação bastante comum é querermos evitar que uma única requisição com erro “derrube” todo o servidor. Por exemplo, o pacote
net/http internamente utiliza algo similar a
recover() para lidar com
panics em handlers.
47 - recover/http-server/http_recover.go
package main
import (
"fmt"
"log"
"net/http"
)
// middlewareRecover é um middleware que encapsula um Handler com recover
func middlewareRecover(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
// Capturando o panic e evitando que o servidor pare
http.Error(w, "Ocorreu um erro interno.", http.StatusInternalServerError)
log.Printf("Panic capturado: %v", err)
}
}()
next.ServeHTTP(w, r)
})
}
func handlerPanic(w http.ResponseWriter, r *http.Request) {
// Forçando um panic
panic("Falha crítica ao processar a requisição!")
}
func handlerOK(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Esta rota funciona normalmente.")
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/panic", handlerPanic)
mux.HandleFunc("/ok", handlerOK)
// Aplicando nosso middleware de recover
srv := &http.Server{
Addr: ":8080",
Handler: middlewareRecover(mux),
}
fmt.Println("Servidor iniciado na porta 8080")
log.Fatal(srv.ListenAndServe())
}
Como funciona:
middlewareRecover é uma função que recebe um http.Handler e retorna outro http.Handler, mas encapsulado em um defer para capturar possíveis panics.
- Se algum panic ocorrer em qualquer rota, ele é capturado pelo
recover() e é retornado um HTTP 500 (erro interno).
- Desta forma, seu servidor continua rodando normalmente, apesar do erro local.
Testando:
- Acesse
http://localhost:8080/ok e veja a mensagem "Esta rota funciona normalmente."
- Acesse
http://localhost:8080/panic e note que, mesmo forçando um panic, o servidor retorna 500 em vez de simplesmente “quebrar”.
4. Exemplo de Recuperação em Goroutines
Um
panic em uma goroutine específica
não encerra diretamente o programa (a não ser que seja a goroutine principal). Mas, se quisermos capturar o
panic em cada goroutine, podemos aplicar a mesma lógica de
recover internamente.
47 - recover/goroutines/recover_goroutines.go
package main
import (
"fmt"
"sync"
"time"
)
func executarTarefa(id int, wg *sync.WaitGroup) {
defer func() {
// Captura qualquer panic da goroutine
if r := recover(); r != nil {
fmt.Printf("[Goroutine %d] Recuperado do panic: %v\n", id, r)
}
wg.Done()
}()
// Simulando condição de erro em goroutine par
if id%2 == 0 {
panic(fmt.Sprintf("Algo deu errado na goroutine %d", id))
}
// Simulação de trabalho
time.Sleep(time.Duration(id) * 100 * time.Millisecond)
fmt.Printf("[Goroutine %d] Tarefa concluída com sucesso!\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go executarTarefa(i, &wg)
}
wg.Wait()
fmt.Println("Todas as goroutines finalizaram (com ou sem erro)!")
}
Explicação:
- A função
executarTarefa defera(adia) um bloco que chama recover(). Se ocorrer um panic, ele será capturado dentro da goroutine.
- No exemplo, forçamos um panic em goroutines com
id par, simulando algum erro específico.
- Assim, cada goroutine pode continuar (ou, no mínimo, tratar o erro de forma controlada), sem encerrar o programa inteiro.
Saída esperada (a ordem pode variar, pois o scheduler de goroutines não é determinístico):
[Goroutine 2] Recuperado do panic: Algo deu errado na goroutine 2
[Goroutine 1] Tarefa concluída com sucesso!
[Goroutine 4] Recuperado do panic: Algo deu errado na goroutine 4
[Goroutine 3] Tarefa concluída com sucesso!
[Goroutine 5] Tarefa concluída com sucesso!
Todas as goroutines finalizaram (com ou sem erro)!
Obs. A ordem pode ser diferente dessa saída acima, isso é absolutamente normal. Quando se trata de goroutines, a ordem de execução delas não é garantida. Cada goroutine é escalonada de maneira independente e assíncrona pelo runtime do Go, então, o momento exato em que cada goroutine imprime suas mensagens pode variar de uma execução para outra.
Conclusão
Nesta aula, vimos como
recover trabalha em conjunto com
defer para capturar
panics e evitar que o programa seja encerrado abruptamente. Em muitos casos, essa abordagem é preferível a deixar o aplicativo “cair”, especialmente quando é possível isolar e tratar o erro localmente.
O uso de
recover deve ser criterioso. Nem sempre é desejável capturar todos os
panics, pois isso pode mascarar problemas graves que deveriam encerrar o programa. Entretanto, em situações como servidores HTTP, goroutines e bibliotecas que não podem parar a aplicação por inteiro,
recover se torna uma ferramenta valiosa para manter a robustez do sistema.
Na próxima aula, vamos explorar
String Functions em Go.
Focaremos em diversas funções nativas para manipulação de strings, tornando nosso desenvolvimento em Go ainda mais eficiente.
Até a próxima aula!