Panic, Defer e Recover
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 57 de 107.
Embora o Go enfatize o retorno de erros, algumas situações são verdadeiramente irrecuperáveis. Para esses casos, o Go fornece panic, defer e recover — um mecanismo para lidar com circunstâncias excepcionais.
panic interrompe a execução normal imediatamente. É reservado para erros de programação, como acessar um índice fora dos limites, não para erros esperados, como entrada de usuário inválida:
func MustGetConfig(key string) string {
value, exists := config[key]
if !exists {
panic("missing required config: " + key)
}
return value
}defer agenda uma função para ser executada quando a função envolvente retorna — seja normalmente ou devido a um pânico. As chamadas adiadas são executadas em ordem inversa (último a entrar, primeiro a sair):
func ProcessFile() {
fmt.Println("Opening file")
defer fmt.Println("Closing file")
fmt.Println("Processing...")
// Saída: Opening file, Processing..., Closing file
}recover captura um panic e retorna a execução normal. Ele só funciona dentro de uma função deferred:
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}Este padrão converte um panic em um erro comum, permitindo que seu programa continue. Use-o com moderação — prefira retornar erros para casos de falha esperados e reserve panic/recover para situações verdadeiramente excepcionais, como estado corrompido ou condições irrecuperáveis.
Desafio
FácilVamos construir uma calculadora de divisão segura que demonstra como usar panic, defer, e recover para lidar com situações excepcionais de forma graciosa. Sua calculadora tentará operações arriscadas e converterá pânicos em erros regulares que podem ser tratados normalmente.
Você organizará seu código em dois arquivos:
calculator.go: Crie a lógica de divisão com recuperação de pânico.Implemente uma função
Divideque recebe dois inteiros e entra em pânico com a mensagem"division by zero"se o divisor for zero. Caso contrário, ela retorna o resultado inteiro da divisão.Implemente uma função
SafeDivideque recebe dois inteiros e retorna tanto um resultado inteiro quanto um erro. Esta função deve:- Usar
defercom uma função anônima para se recuperar de qualquer pânico - Se um pânico for recuperado, convertê-lo em um erro com o formato
"calculation error: [panic message]" - Chamar a função
Divideinternamente - Retornar o resultado e
nilse for bem-sucedido, ou zero e o erro se um pânico foi recuperado
- Usar
main.go: Leia dois inteiros da entrada e useSafeDividepara realizar o cálculo com segurança. Também demonstre odeferimprimindo mensagens de limpeza na ordem correta.Sua função principal deve:
- Imprimir
Starting calculationno início - Usar
deferpara agendar a impressão deCleanup complete - Usar outro
deferpara agendar a impressão deReleasing resources - Chamar
SafeDividecom os valores de entrada - Imprimir
Result: [value]ouError: [error message]com base no resultado
- Imprimir
As seguintes entradas serão fornecidas:
- Linha 1: Primeiro inteiro (dividendo)
- Linha 2: Segundo inteiro (divisor)
Lembre-se de que as chamadas adiadas (deferred calls) são executadas na ordem inversa (last-in, first-out), portanto, suas mensagens de limpeza devem aparecer na ordem oposta àquela em que foram adiadas.
Por exemplo, dados 20 e 4, sua saída deve ser:
Starting calculation
Result: 5
Releasing resources
Cleanup completeE dados 10 e 0, sua saída deve ser:
Starting calculation
Error: calculation error: division by zero
Releasing resources
Cleanup completeFolha de consulta
Go fornece panic, defer e recover para lidar com circunstâncias excepcionais.
panic interrompe a execução normal imediatamente e é reservado para erros de programação irrecuperáveis:
func MustGetConfig(key string) string {
value, exists := config[key]
if !exists {
panic("missing required config: " + key)
}
return value
}defer agenda uma função para ser executada quando a função envolvente retorna. Chamadas adiadas (deferred) são executadas em ordem inversa (última a entrar, primeira a sair):
func ProcessFile() {
fmt.Println("Opening file")
defer fmt.Println("Closing file")
fmt.Println("Processing...")
// Saída: Opening file, Processing..., Closing file
}recover captura um panic e retorna a execução normal. Ele só funciona dentro de uma função adiada (deferred):
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}Este padrão converte um panic em um erro comum. Prefira retornar erros para falhas esperadas e reserve panic/recover para situações verdadeiramente excepcionais.
Experimente você mesmo
package main
import (
"fmt"
)
func main() {
// Ler entrada
var dividend, divisor int
fmt.Scanln(÷nd)
fmt.Scanln(&divisor)
// Imprimir mensagem inicial
fmt.Println("Starting calculation")
// TODO: Usar defer para agendar a mensagem "Cleanup complete"
// TODO: Usar defer para agendar a mensagem "Releasing resources"
// Lembre-se: chamadas adiadas (deferred) executam na ordem LIFO (last-in, first-out)
// TODO: Chamar SafeDivide com os valores de entrada
// TODO: Imprimir "Result: [value]" ou "Error: [error message]"
// com base no fato de um erro ter sido retornado ou não
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP em Go
Arquivos ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool