Panic, Defer y Recover
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 57 de 107.
Aunque Go enfatiza el retorno de errores, algunas situaciones son verdaderamente irrecuperables. Para estos casos, Go proporciona panic, defer, y recover—un mecanismo para manejar circunstancias excepcionales.
panic detiene la ejecución normal inmediatamente. Está reservado para errores de programación como acceder a un índice fuera de los límites, no para errores esperados como una entrada de usuario inválida:
func MustGetConfig(key string) string {
value, exists := config[key]
if !exists {
panic("missing required config: " + key)
}
return value
}defer programa una función para que se ejecute cuando la función que la rodea retorna, ya sea de forma normal o debido a un pánico (panic). Las llamadas diferidas se ejecutan en orden inverso (último en entrar, primero en salir):
func ProcessFile() {
fmt.Println("Opening file")
defer fmt.Println("Closing file")
fmt.Println("Processing...")
// Salida: Opening file, Processing..., Closing file
}recover captura un pánico y retoma la ejecución normal. Solo funciona dentro de una función diferida:
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}Este patrón convierte un panic en un error regular, permitiendo que su programa continúe. Úselo con moderación: prefiera devolver errores para los casos de falla esperados y reserve panic/recover para situaciones verdaderamente excepcionales, como un estado corrupto o condiciones irrecuperables.
Desafío
FácilVamos a construir una calculadora de división segura que demuestre cómo usar panic, defer y recover para manejar situaciones excepcionales con elegancia. Tu calculadora intentará realizar operaciones arriesgadas y convertirá los pánicos en errores regulares que pueden ser manejados normalmente.
Organizarás tu código en dos archivos:
calculator.go: Crea la lógica de división con recuperación de pánico.Implementa una función
Divideque tome dos enteros y lance un pánico con el mensaje"division by zero"si el divisor es cero. De lo contrario, devuelve el resultado entero de la división.Implementa una función
SafeDivideque tome dos enteros y devuelva tanto un resultado entero como un error. Esta función debe:- Usar
defercon una función anónima para recuperarse de cualquier pánico - Si se recupera un pánico, convertirlo en un error con el formato
"calculation error: [panic message]" - Llamar a la función
Divideinternamente - Devolver el resultado y
nilsi tiene éxito, o cero y el error si se recuperó un pánico
- Usar
main.go: Lee dos enteros de la entrada y usaSafeDividepara realizar el cálculo de forma segura. También demuestradeferimprimiendo mensajes de limpieza en el orden correcto.Tu función principal debe:
- Imprimir
Starting calculational principio - Usar
deferpara programar la impresión deCleanup complete - Usar otro
deferpara programar la impresión deReleasing resources - Llamar a
SafeDividecon los valores de entrada - Imprimir ya sea
Result: [value]oError: [error message]según el resultado
- Imprimir
Se proporcionarán las siguientes entradas:
- Línea 1: Primer entero (dividendo)
- Línea 2: Segundo entero (divisor)
Recuerda que las llamadas diferidas se ejecutan en orden inverso (último en entrar, primero en salir), por lo que tus mensajes de limpieza deben aparecer en el orden opuesto a como fueron diferidos.
Por ejemplo, dados 20 y 4, tu salida debería ser:
Starting calculation
Result: 5
Releasing resources
Cleanup completeY dados 10 y 0, tu salida debería ser:
Starting calculation
Error: calculation error: division by zero
Releasing resources
Cleanup completeHoja de referencia
Go proporciona panic, defer, y recover para manejar circunstancias excepcionales.
panic detiene la ejecución normal inmediatamente y está reservado para errores de programación irrecuperables:
func MustGetConfig(key string) string {
value, exists := config[key]
if !exists {
panic("missing required config: " + key)
}
return value
}defer programa una función para que se ejecute cuando la función circundante retorna. Las llamadas diferidas se ejecutan en orden inverso (último en entrar, primero en salir):
func ProcessFile() {
fmt.Println("Opening file")
defer fmt.Println("Closing file")
fmt.Println("Processing...")
// Salida: Opening file, Processing..., Closing file
}recover captura un pánico y restaura la ejecución normal. Solo funciona dentro de una función diferida:
func SafeOperation() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
riskyOperation()
return nil
}Este patrón convierte un pánico en un error regular. Es preferible devolver errores para los fallos esperados y reservar panic/recover para situaciones verdaderamente excepcionales.
Pruébalo tú mismo
package main
import (
"fmt"
)
func main() {
// Leer entrada
var dividend, divisor int
fmt.Scanln(÷nd)
fmt.Scanln(&divisor)
// Imprimir mensaje de inicio
fmt.Println("Starting calculation")
// TODO: Usar defer para programar el mensaje "Cleanup complete"
// TODO: Usar defer para programar el mensaje "Releasing resources"
// Recordatorio: las llamadas diferidas se ejecutan en orden LIFO (last-in, first-out)
// TODO: Llamar a SafeDivide con los valores de entrada
// TODO: Imprimir "Result: [value]" o "Error: [error message]"
// basado en si se devolvió un error
}
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