DMarlon Studio

Lección 6 · 6 de 20

defer, panic y recover · Go

Aplace llamadas con defer, vea en qué orden se ejecutan y convierta un panic en un error normal con recover, en Go.

Go · defer, panic y recover en DMarlon Studio: el código del ejemplo y su salida en la terminal

`defer` aplaza una llamada hasta que la función que la contiene termina, salga por donde salga. En importFile, defer fmt.Println("close", name) se escribe justo después de abrir: así el cierre queda al lado de la apertura y nunca se olvida, aunque la función tenga varios return.

Las llamadas aplazadas se apilan y se ejecutan en orden inverso (la última, la primera): por eso salen undo step 3, 2, 1 y, al final, close. Los argumentos se evalúan al hacer el `defer`, no al ejecutarlo: cada step queda fijado con su valor de ese momento.

Un `panic` es un fallo que detiene el programa: aquí lo provoca total / len(scores) con una lista vacía, una división entre cero. El panic sube por las funciones ejecutando sus defer; si nadie lo detiene, el programa termina con un mensaje de error.

`recover` lo detiene, pero solo dentro de una función aplazada. average aplaza una función anónima que llama a recover(): si hubo panic, devuelve su valor (runtime error: integer divide by zero) y la función lo convierte en un error. Como los resultados tienen nombre, (avg int, err error), la función aplazada puede cambiar err antes de que average devuelva.

El defer de main se escribió el primero y sale el último. En Go, los errores normales se devuelven como valores; panic y recover se reservan para fallos de programación o para que un servidor no se caiga entero por una sola petición.

Código · main.go

package mainimport "fmt"func importFile(name string) {	fmt.Println("open", name)	defer fmt.Println("close", name) // runs when importFile returns	for step := 1; step <= 3; step++ {		defer fmt.Println("  undo step", step) // arguments are evaluated now	}	fmt.Println("  importing rows...")}func average(scores []int) (avg int, err error) { // recover turns a panic into an error	defer func() {		if r := recover(); r != nil {			err = fmt.Errorf("recovered: %v", r) // sets the named result		}	}()	total := 0	for _, s := range scores {		total += s	}	return total / len(scores), nil // panics if scores is empty}func main() {	defer fmt.Println("main: deferred until the very end")	importFile("orders.csv")	for _, scores := range [][]int{{90, 85, 95}, {}} {		avg, err := average(scores)		fmt.Println("average:", avg, "| error:", err)	}}

Salida · go run main.go

open orders.csv
  importing rows...
  undo step 3
  undo step 2
  undo step 1
close orders.csv
average: 90 | error: <nil>
average: 0 | error: recovered: runtime error: integer divide by zero
main: deferred until the very end

Para recordar

  • defer ejecuta la llamada al terminar la función.
  • Varios defer se ejecutan en orden inverso.
  • Los argumentos de un defer se evalúan en el momento de escribirlo.
  • recover solo funciona dentro de una función aplazada.
  • Con resultados con nombre, un defer puede cambiar lo que se devuelve.

Ejercicio

Cambie el bucle de importFile por defer func() { fmt.Println(" undo step", step) }() y observe qué número sale ahora: la función anónima lee step al ejecutarse, no al aplazarse.