En Go, se utilizan las llamadas diferidas (defer) junto con cierres anónimos (closures) para garantizar la limpieza o finalización del trabajo con recursos. Este patrón permite agrupar la lógica de limpieza y manejar errores de manera adecuada, garantizando un código legible y confiable.
Historia de la cuestión:
Defer se ha tomado de otros lenguajes y simplifica mucho la vida del desarrollador de Go. La combinación de defer y cierres se ha convertido en un estándar para asegurar la limpieza de archivos, conexiones y cualquier recurso externo en bloques donde puede haber muchas salidas (return, panic).
Problema:
Con una lógica compleja de limpieza de recursos (por ejemplo, archivos, conexiones, bloques), es necesario garantizar que incluso en caso de error o salida de la función, se realice la limpieza. Con un uso incorrecto, pueden surgir pérdidas, un orden incorrecto de limpieza o errores sin sentido.
Solución:
Usar defer con una función anónima (closure) para:
Ejemplo de código:
package main import ( "fmt" "os" ) func WriteFileDemo(filename string) (err error) { f, err := os.Create(filename) if err != nil { return } defer func() { cerr := f.Close() if cerr != nil && err == nil { err = cerr } }() // Lógica de trabajo con el archivo fmt.Fprintln(f, "Hola mundo") return // defer se ejecutará incluso si aquí está return } func main() { if err := WriteFileDemo("test.txt"); err != nil { fmt.Println("Error:", err) } }
Características clave:
¿Cuándo se fijan las variables utilizadas dentro del cierre diferido: en el momento de la declaración defer o en la llamada real?
Se fijan en el momento de la declaración defer, pero si el cierre accede a las variables por referencia, se utilizarán sus valores en el momento de la ejecución del defer. A veces esto lleva a resultados inesperados.
for i := 0; i < 3; i++ { defer func() { fmt.Println(i) }() // imprimirá 2, 2, 2 }
¿Se pueden pasar valores al cierre a través de parámetros para evitar la captura por referencia?
Sí, se pueden declarar parámetros para la función anónima y pasarles los valores actuales, de modo que los valores se ‘congelen’ como copias.
for i := 0; i < 3; i++ { defer func(n int) { fmt.Println(n) }(i) // imprimirá 2, 1, 0 }
¿Qué hacer si se detecta un pánico en el cierre diferido? ¿Cómo manejarlo?
Dentro del cierre, se utiliza la estructura recover() para evitar que el pánico salga al exterior y realizar una recuperación suave de la funcionalidad.
defer func() { if r := recover(); r != nil { log.Println("Recuperado:", r) } }()
El código abre varios archivos, pero se olvida de poner defer f.Close(). Al producirse un error, devuelve el control y parte de los archivos no se limpian, lo que provoca una pérdida de recursos.
Ventajas:
Desventajas:
Se utiliza un cierre diferido para todas las operaciones de venta: cerrar cuidadosamente el canal de archivos y manejar el error, incluso si el archivo no se ha escrito completamente.
Ventajas:
Desventajas: