One place for hosting & domains

      manejar

      Cómo manejar errores en Go


      Un código sólido debe reaccionar de forma adecuada en circunstancias imprevistas, como entradas incorrectas de los usuarios o conexiones de red o discos defectuosos. El manejo de errores es el proceso de identificar cuando sus programas se encuentran en un estado imprevisto y de tomar medidas para registrar información de diagnóstico para una depuración posterior.

      A diferencia de otros lenguajes que requieren que los desarrolladores manejen los errores con una sintaxis especial, los errores en Go son valores del tipo error que se devuelven de funciones como cualquier otro valor. Para manejar errores en Go, debemos examinar los errores que pueden devolver las funciones, determinar si se produjo un error y tomar las medidas adecuadas para proteger los datos e informarles a los usuarios y los operadores que se produjo un error.

      Creación de errores

      Para poder manejar errores, debemos crear algunos primero. La biblioteca estándar ofrece dos funciones incorporadas para crear errores: errors.New y fmt.Errorf. Estas dos funciones le permiten especificar un mensaje de error personalizado para presentarlo, posteriormente, a sus usuarios.

      errors.New tiene un solo argumento: un mensaje de error con una cadena que puede personalizar para avisarles a sus usuarios cuál fue el problema.

      Intente ejecutar el ejemplo que se indica a continuación para ver un error creado por errors.New incorporado a una salida estándar:

      package main
      
      import (
          "errors"
          "fmt"
      )
      
      func main() {
          err := errors.New("barnacles")
          fmt.Println("Sammy says:", err)
      }
      

      Output

      Sammy says: barnacles

      Usamos la función errors.New de la biblioteca estándar para crear un nuevo mensaje de error con la cadena "barnacles" como mensaje de error. Aquí, seguimos la convención al usar minúsculas para el mensaje de error, tal como se sugiere en la Guía de Estilo del Lenguaje de Programación Go.

      Por último, usamos la función fmt.Println para combinar nuestro mensaje de error con "Sammy says:".

      La función fmt.Errorf le permite crear un mensaje de error de forma dinámica. Su primer argumento es una cadena que contiene su mensaje de error con valores de marcadores de posición, como %s para cadenas y %d para enteros. fmt.Errorf interpola los argumentos que siguen esta cadena de formato en esos marcadores de posición en orden:

      package main
      
      import (
          "fmt"
          "time"
      )
      
      func main() {
          err := fmt.Errorf("error occurred at: %v", time.Now())
          fmt.Println("An error happened:", err)
      }
      

      Output

      An error happened: Error occurred at: 2019-07-11 16:52:42.532621 -0400 EDT m=+0.000137103

      Usamos la función fmt.Errorf para crear un mensaje de error que incluya la hora actual. La cadena de formato que proporcionamos a fmt.Errorf contiene la directiva de formato %v que le indica a fmt.Errorf que use el formato predeterminado para el primer argumento proporcionado después de la cadena de formato. Ese argumento será la hora actual, que se proporciona mediante la función time.Now de la biblioteca estándar. De manera similar al ejemplo anterior, combinamos nuestro mensaje de error con un prefijo corto y enviamos el resultado a la salida estándar utilizando la función fmt.Println.

      Manejo de errores

      En general, no vería un error creado como este para utilizarlo de inmediato con ningún otro propósito, como en el ejemplo anterior. En la práctica, es mucho más frecuente crear un error y devolverlo de una función cuando ocurre un problema. Entonces, quienes invoquen esa función, utilizarán una instrucción if para ver si el error ocurrió o nil, un valor sin inicializar.

      El ejemplo siguiente incluye una función que siempre devuelve un error. Cuando ejecute el programa, observe que produce la misma salida que el ejemplo anterior, a pesar de que, ahora, el error se está devolviendo de una función. La declaración de un error en una ubicación distinta no modifica el mensaje del error.

      package main
      
      import (
          "errors"
          "fmt"
      )
      
      func boom() error {
          return errors.New("barnacles")
      }
      
      func main() {
          err := boom()
      
          if err != nil {
              fmt.Println("An error occurred:", err)
              return
          }
          fmt.Println("Anchors away!")
      }
      

      Output

      An error occurred: barnacles

      Aquí, definimos una función denominada boom() que devuelve un único error que creamos utilizando errors.New. Luego, llamamos a esta función y captamos el error con la línea err := boom(). Una vez asignemos este error, comprobaremos si estaba presente con el condicional if err ! = nil. Aquí, el condicional siempre evaluará a true, dado que siempre estamos devolviendo un error de boom().

      Esto no siempre será así, por lo que es recomendable contar con casos lógicos de manipulación en los que el error no esté presente (nil) y casos en los que lo esté. Cuando el error está presente, usamos fmt.Println para mostrar nuestro error junto con un prefijo, como hemos hecho en ejemplos anteriores. Por último, usamos una instrucción return para omitir la ejecución de fmt.Println("Anchors away!"), dado que solo se debe ejecutar cuando no se produce ningún error.

      Nota: La construcción if err ! = nil que se muestra en el último ejemplo es el caballo de batalla de la manipulación de errores en el lenguaje de programación Go. Donde sea que una función pueda producir un error, es importante utilizar una instrucción if para determinar su presencia. De esta manera, el código idiomático Go tiene, naturalmente, su lógica “happy path” en el primer nivel de indentación, y toda la lógica “sad path”, en el segundo.

      Las instrucciones if tienen una cláusula opcional de asignación que puede utilizarse para ayudar a resumir la invocación de una función y el manejo de sus errores.

      Ejecute el siguiente programa para ver la misma salida de nuestro ejemplo anterior, pero, esta vez, con una instrucción if compuesta para reducir el texto estándar:

      package main
      
      import (
          "errors"
          "fmt"
      )
      
      func boom() error {
          return errors.New("barnacles")
      }
      
      func main() {
          if err := boom(); err != nil {
              fmt.Println("An error occurred:", err)
              return
          }
          fmt.Println("Anchors away!")
      }
      

      Output

      An error occurred: barnacles

      Al igual que antes, tenemos una función, boom(), que siempre devuelve un error. Asignamos el error devuelto de boom() a err como la primera parte de la instrucción if. De esta manera, esa variable err está disponible en la segunda parte de la instrucción if, después del punto y coma. Comprobamos si el error estaba presente y mostramos nuestro error con una cadena de prefijo corta, como hemos hecho anteriormente.

      En esta sección, aprendimos a manejar funciones que solo devuelven un error. Estas funciones son habituales, pero también es importante poder manejar errores de funciones que pueden devolver varios valores.

      Devolución de errores junto con valores

      Las funciones que devuelven un solo valor de error suelen ser las que producen algún cambio de estado, como la inserción de filas a una base de datos. También es frecuente escribir funciones que devuelven un valor si se completaron con éxito junto con un posible error si la función falló. Go permite que las funciones devuelvan más de un resultado, lo que puede utilizarse para devolver simultáneamente un valor y un tipo de error.

      Para crear una función que devuelva más de un valor, enumeramos los tipos de cada valor devuelto dentro de paréntesis en la firma de la función. Por ejemplo, una función capitalize que devuelve una string y un error se declararía utilizando func capitalize(name string) (string, error) {}. La parte (string, error) le indica al compilador de Go que esta función devolverá una string y un error, en ese orden.

      Ejecute el programa que se indica a continuación para ver la salida de una función que devuelve una string y un error:

      package main
      
      import (
          "errors"
          "fmt"
          "strings"
      )
      
      func capitalize(name string) (string, error) {
          if name == "" {
              return "", errors.New("no name provided")
          }
          return strings.ToTitle(name), nil
      }
      
      func main() {
          name, err := capitalize("sammy")
          if err != nil {
              fmt.Println("Could not capitalize:", err)
              return
          }
      
          fmt.Println("Capitalized name:", name)
      }
      

      Output

      Capitalized name: SAMMY

      Definimos capitalize() como una función que toma una cadena (el nombre que se escribirá en mayúsculas), y devuelve una cadena y un valor de error. En main(), invocamos capitalize() y asignamos los dos valores devueltos de la función a las variables name y err al separarlos con comas en el lado izquierdo del operador :=. A continuación, ejecutamos nuestra comprobación if err ! = nil, como en ejemplos anteriores, y mostramos el error en la salida estándar utilizando fmt.Println si el error estaba presente. Si no hubo errores, mostramos Capitalized name: SAMMY.

      Intente cambiar la cadena "sammy" en name, err := capitalize("sammy") por la cadena vacía ("") y recibirá, en su lugar, el error Could not capitalize: no name provided.

      La función capitalize devolverá un error cuando quienes invoquen la función proporcionen una cadena vacía para el parámetro name. Cuando el parámetro name no es la cadena vacía, capitalize() utiliza strings.ToTitle para escribir, en mayúsculas, el parámetro name y devuelve nil como valor de error.

      Hay algunas convenciones sutiles que siguen este ejemplo que son típicas del código Go, pero el compilador de Go no las aplica. Cuando una función devuelve varios valores, con un error incluido, la convención requiere que el error se devuelva como último elemento. Al devolver un error de una función con varios valores de retorno, el código idiomático Go también establecerá un valor de cero para cada valor non-error. Los valores de cero son, por ejemplo, una cadena vacía para cadenas, 0 para enteros, una estructura vacía para tipos de estructura y nil para tipos de punteros e interfaces, por nombrar algunos. Abordaremos los valores de cero con más detalle en nuestro tutorial sobre variables y constantes.

      Reducción del texto estándar

      Respetar estas convenciones puede volverse tedioso en situaciones en las que se devuelven muchos valores de una función. Podemos utilizar una función anónima para ayudar a reducir el texto estándar. Las funciones anónimas son procedimientos que se asignan a variables. A diferencia de las funciones que definimos en ejemplos anteriores, solo están disponibles en las funciones donde se declaran. Esto las hace perfectas para actuar como fragmentos cortos de lógica auxiliar reutilizable.

      El programa que se indica a continuación modifica el último ejemplo para incluir la longitud del nombre que escribiremos en mayúsculas. Dado que tiene tres valores que devolver, el manejo de errores podría tornarse engorroso sin una función anónima de ayuda:

      package main
      
      import (
          "errors"
          "fmt"
          "strings"
      )
      
      func capitalize(name string) (string, int, error) {
          handle := func(err error) (string, int, error) {
              return "", 0, err
          }
      
          if name == "" {
              return handle(errors.New("no name provided"))
          }
      
          return strings.ToTitle(name), len(name), nil
      }
      
      func main() {
          name, size, err := capitalize("sammy")
          if err != nil {
              fmt.Println("An error occurred:", err)
          }
      
          fmt.Printf("Capitalized name: %s, length: %d", name, size)
      }
      

      Output

      Capitalized name: SAMMY, length: 5

      Ahora, dentro de main(), capturamos los tres argumentos devueltos de capitalize como name, size y err, respectivamente. Luego, comprobamos si capitalize devolvió un error al verificar si la variable err era distinta de nil. Es importante hacerlo antes de intentar utilizar cualquiera de los demás valores que devuelve capitalize, dado que la función anónima, handle, podría establecer esos valores en cero. Dado que no ocurrió ningún error porque proporcionamos la cadena "sammy", mostramos el nombre en mayúsculas y su longitud.

      Una vez más, puede intentar cambiar "sammy" por la cadena vacía ("") para mostrar el caso de error (An error occurred: no name provided).

      Dentro de capitalize, definimos la variable handle como una función anónima. Toma un solo error y devuelve valores idénticos en el mismo orden que los valores de retorno de capitalize. handle establece esos valores en cero y reenvía el error pasado como su argumento, como el valor de retorno final. Al usar esto, podemos devolver cualquier error que se detecte en capitalize al utilizar la instrucción return delante de la invocación a handle con el error como su parámetro.

      Recuerde que capitalize siempre debe devolver tres valores, dado que así es como definimos la función. A veces, no queremos lidiar con todos los valores que una función podría devolver. Afortunadamente, tenemos cierta flexibilidad en la manera en que podemos utilizar estos valores en el lado de la asignación.

      Manejo de errores de funciones que devuelven varios valores

      Cuando una función devuelve muchos valores, Go requiere que asignemos una variable a cada uno de ellos. En el último ejemplo, lo hacemos al proporcionar nombres para los dos valores que devuelve la función capitalize. Estos nombres se deben separar con comas y tienen que aparecer en lado izquierdo del operador :=. El primer valor devuelto de capitalize se asignará a la variable name, y el segundo valor (el error) se asignará a la variable err. A veces, solo nos interesa el valor de error. Puede descartar cualquier valor no deseado que devuelva una función al utilizar el nombre de variable especial _.

      En el programa que se indica a continuación, modificamos nuestro primer ejemplo con la función capitalize para producir un error al pasar la cadena vacía (""). Intente ejecutar este programa para ver cómo podemos examinar solo el error al descartar el primer valor devuelto con la variable _:

      package main
      
      import (
          "errors"
          "fmt"
          "strings"
      )
      
      func capitalize(name string) (string, error) {
          if name == "" {
              return "", errors.New("no name provided")
          }
          return strings.ToTitle(name), nil
      }
      
      func main() {
          _, err := capitalize("")
          if err != nil {
              fmt.Println("Could not capitalize:", err)
              return
          }
          fmt.Println("Success!")
      }
      

      Output

      Could not capitalize: no name provided

      Esta vez, dentro de la función main(), asignamos el nombre en mayúsculas (la string que se devuelve primero) a la variable de guion bajo (_). Al mismo tiempo, asignamos el error devuelto por capitalize a la variable err. A continuación, comprobamos si el error estaba presente en el condicional if err ! = nil. Dado que predefinimos una cadena vacía en el código como argumento de capitalize en la línea _, err := capitalize(""), este condicional siempre evaluará a true. Esto produce la salida "Could not capitalize: no name provided" que se muestra al invocar la función fmt.Println dentro del cuerpo de la instrucción if. Después de esto. el return omitirá fmt.Println("Success!").

      Conclusión

      Hemos visto muchas maneras de crear errores utilizando la biblioteca estándar y cómo crear funciones que devuelvan errores de una manera idiomática. En este tutorial, hemos logrado crear con éxito varios errores utilizando la biblioteca estándar errors.New y funciones fmt.Errorf. En tutoriales futuros, veremos cómo crear nuestros propios tipos de errores personalizados para transmitir más información a los usuarios.



      Source link

      Cómo manejar panics en Go


      Introducción

      Los errores que un programa encuentra se dividen en dos categorías generales: los que el programador anticipa y los que no anticipa. La interfaz error que abarcamos en nuestros dos artículos anteriores sobre el manejo de errores se encarga principalmente de los errores que esperamos a medida que escribimos programas Go. La interfaz error incluso nos permite reconocer la atípica posibilidad de que se produzca un error debido a las invocaciones de la función para que podamos responder de forma apropiada en esas situaciones.

      Los “panics” corresponden a la segunda categoría de errores; es decir, la de aquellos no anticipados por el programador. Estos errores imprevistos hacen que el programa finalice de forma espontánea y que se cierre el programa Go en ejecución. Las equivocaciones comunes a menudo generan panics. A lo largo de este tutorial, examinaremos varias formas en que las operaciones comunes pueden producir panics en Go y también veremos formas de evitarlos. También usaremos instrucciones defer junto con la función recover para captar “panic” antes de que puedan cerrar inesperadamente nuestros programas de Go en ejecución.

      Comprender los panics

      Existen ciertas operaciones en Go que muestran panics y detienen el programa de forma automática. Entre las operaciones comunes incluyen se incluyen las de exceder la capacidad de indexación de una matriz, realizar afirmaciones de tipo, invocar métodos en punteros nulos, usar mutexes de forma incorrecta e intentar trabajar con canales cerrados. La mayoría de estas situaciones se deben a equivocaciones que se cometen durante la programación y que el compilador no puede detectar mientras compila su programa.

      Debido a que entre los panics se incluyen detalles que son útiles para resolver un problema, los desarrolladores normalmente utilizan los panics como una indicación de que cometieron un error durante el desarrollo de un programa.

      Panics fuera de los límites

      Cuando intente acceder a un índice más allá de la extensión de un segmento o de la capacidad de una matriz, el tiempo de ejecución de Go generará un panic.

      En el siguiente ejemplo se comete la equivocación común de intentar acceder al último elemento de un segmento usando la extensión del segmento que el builtin len muestra. Intente ejecutar este código para ver por la razón por la cual esto puede producir un panic:

      package main
      
      import (
          "fmt"
      )
      
      func main() {
          names := []string{
              "lobster",
              "sea urchin",
              "sea cucumber",
          }
          fmt.Println("My favorite sea creature is:", names[len(names)])
      }
      

      Esto generará el siguiente resultado:

      Output

      panic: runtime error: index out of range [3] with length 3 goroutine 1 [running]: main.main() /tmp/sandbox879828148/prog.go:13 +0x20

      El nombre del resultado del panic proporciona una pista: panic: runtime error: index out of range. Creamos un segmento con tres criaturas marinas. Luego, intentamos obtener el último elemento del segmento indexando ese segmento con su extensión, a través de la función builtin len. Recuerde que los segmentos y las matrices se basan en cero, de modo que el primer elemento sea cero y el último elemento de este segmento se encuentre en el índice 2. Debido a que intentamos acceder al segmento en el tercer índice, 3, no hay ningún elemento en el segmento que se pueda mostrar porque está más allá de los límites de este. El tiempo de ejecución no tiene otra opción que finalizar y salir, ya que le pedimos que hiciera algo imposible. Go tampoco puede demostrar durante la compilación que este código intentará hacer esto, de modo que el compilador no puede captarlo.

      Observe también que el código posterior no se ejecutó. Esto se debe a que un panic es un evento que detiene completamente la ejecución de su programa Go. El mensaje producido contiene varios datos útiles para diagnosticar la causa del panic.

      Anatomía de un panic

      Los panics constan de un mensaje que indica la causa que los originó y un seguimiento de pila que le permite a localizar la parte del código en la que se produjo el panic.

      La primera parte de cualquier panic es el mensaje. Siempre empezará con la cadena panic: y a esta le seguirá una cadena que variará según la causa del panic. El panic del ejercicio anterior tiene el siguiente mensaje:

      panic: runtime error: index out of range [3] with length 3
      

      La cadena runtime error: situada después del prefijo panic: indica que el panic se generó a través del tiempo de ejecución del lenguaje. Este panic nos dice que intentamos usar un índice [3] que estaba fuera del rango de la extensión 3 del segmento.

      Después de este mensaje se encuentra el seguimiento de pila. Los seguimientos de pila forman un mapa que podemos seguir para determinar con exactitud la línea de código que estaba en ejecución cuando se generó el panic y la forma en que un código anterior invocó ese código.

      goroutine 1 [running]:
      main.main()
          /tmp/sandbox879828148/prog.go:13 +0x20
      

      Este seguimiento de pila, del ejemplo anterior, muestra que nuestro programa generó el panic desde el archivo /tmp/sandbox879828148/prog.go en la línea número 13. También nos dice que este panic se generó en la función main() desde el paquete main.

      El seguimiento de pila se divide en bloques separados, uno para cada goroutine en su programa. Cada ejecución del programa de Go se concreta mediante una o más goroutines que pueden ejecutar de forma independiente y simultánea partes de su código de Go. Cada bloque comienza con el encabezado goroutine X [state]:. El encabezado le proporciona el número de ID de la goroutine junto con el estado en que estaba cuando se produjo el panic. Después del encabezado, el seguimiento de pila muestra la función que el programa ejecutaba cuando se produjo el panic, junto con el nombre de archivo y el número de línea en los que se ejecutaba la función.

      El panic del ejemplo anterior se generó a través de un acceso fuera de límites a un segmento. También se pueden generar panics cuando se invocan métodos en punteros que no están establecidos.

      Receptores nil

      El lenguaje de programación Go tiene punteros para referirse a una instancia específica de algún tipo existente en la memoria del equipo en el tiempo de ejecución. Los punteros pueden asumir el valor nil para indicar que no apuntan a nada. Cuando intentamos invocar métodos en un puntero que tenga el valor nil, el tiempo de ejecución de Go generará un panic. De forma similar, las variables que son tipos de interfaz también producirán panics cuando se invoquen métodos en ellas. Para ver los panics generados en estos casos, pruebe con el siguiente ejemplo:

      package main
      
      import (
          "fmt"
      )
      
      type Shark struct {
          Name string
      }
      
      func (s *Shark) SayHello() {
          fmt.Println("Hi! My name is", s.Name)
      }
      
      func main() {
          s := &Shark{"Sammy"}
          s = nil
          s.SayHello()
      }
      

      Los panics producidos tendrán este aspecto:

      Output

      panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xdfeba] goroutine 1 [running]: main.(*Shark).SayHello(...) /tmp/sandbox160713813/prog.go:12 main.main() /tmp/sandbox160713813/prog.go:18 +0x1a

      En este ejemplo, definimos una estructura llamada Shark. Shark tiene en su receptor de puntero un método definido llamado SayHello que imprimirá un saludo de forma estándar cuando se invoque. En el cuerpo de nuestra función main, creamos una nueva instancia de esta estructura Shark y solicitamos un puntero a ella usando el operador &. Este puntero se asigna a la variable s. Luego, volvemos a asignar la variable s al valor nil con la instrucción s = nil. Finalmente, intentamos invocar el método SayHello en la variable s. En vez de recibir un mensaje favorable de Sammy, recibimos un panic que indica que hemos intentando acceder a una dirección no válida de la memoria. Debido a que la variable s es nil, cuando se invoca la función SayHello, intenta acceder al campo Name en el tipo *Shark. Debido a que éste es un receptor de puntero, y el receptor en este caso es nil, se produce un panic porque no puede eliminar la referencia de un puntero nil.

      Aunque fijamos s en nil de forma explícita en este ejemplo, en la práctica esto sucede de forma menos obvia. Cuando vea panics relacionados con nil pointer dereference, asegúrese de haber asignado adecuadamente cualquier variable de puntero que pueda haber creado.

      Los panics generados a partir de los punteros nil y los accesos fuera de límites son dos panics comunes que genera el tiempo de ejecución. También es posible generar un panic manualmente usando una función “builtin”.

      Usar la función bultin panic

      También podemos generar panics propios usando la función integrada panic. Requiere una cadena única como argumento, que es el mensaje que el panic producirá. Normalmente, este mensaje es menos detallado que la reescritura de nuestro código para mostrar un error. Además, podemos usar esto en nuestros propios paquetes para indicar a los desarrolladores que pueden haber cometido un error al utilizar el código de nuestro paquete. Siempre que sea posible, la práctica recomendada es intentar mostrar valores error a los consumidores de nuestro paquete.

      Ejecute este código para ver un panic generado a partir de una función invocada desde otra función:

      package main
      
      func main() {
          foo()
      }
      
      func foo() {
          panic("oh no!")
      }
      

      El resultado del panic tiene este aspecto:

      Output

      panic: oh no! goroutine 1 [running]: main.foo(...) /tmp/sandbox494710869/prog.go:8 main.main() /tmp/sandbox494710869/prog.go:4 +0x40

      Aquí definimos una función foo que invoca el builtin panic con la cadena "oh no!". Esta función se invoca mediante nuestra función main. Observe que el resultado contiene el mensaje panic: oh no! y el seguimiento de pila muestra una “goroutine” única con dos líneas en el seguimiento de pila: una para la función main() y una para nuestra función foo().

      Vimos que los panics aparecen para cerrar nuestro programa cuando se generan. Esto puede generar problemas cuando existen recursos abiertos que deben cerrarse correctamente. Go proporciona un mecanismo para ejecutar código siempre, incluso cuando aparece un panic.

      Funciones diferidas

      Su programa puede tener recursos que deben limpiarse adecuadamente, incluso mientras el tiempo de ejecución procesa un panic. Go le permite diferir la ejecución de la invocación de una función hasta que la función de invocación de esta complete la ejecución. Las funciones diferidas se ejecutan incluso en presencia de un panic y se usan como mecanismo de seguridad para brindar protección contra la naturaleza caótica de los panics. Las funciones se difieren invocándolas de la forma habitual y añadiendo luego un prefijo a toda la instrucción con la palabra clave defer, como en defer sayHello(). Ejecute este ejemplo para ver cómo se puede imprimir un mensaje aunque se haya producido un panic:

      package main
      
      import "fmt"
      
      func main() {
          defer func() {
              fmt.Println("hello from the deferred function!")
          }()
      
          panic("oh no!")
      }
      

      El resultado producido a partir de este ejemplo tendrá este aspecto:

      Output

      hello from the deferred function! panic: oh no! goroutine 1 [running]: main.main() /Users/gopherguides/learn/src/github.com/gopherguides/learn//handle-panics/src/main.go:10 +0x55

      En la función main de este ejemplo, primero aplicamos defer a una invocación de una función anónima que imprime el mensaje "hello from the deferred function!". La función main produce inmediatamente un panic usando la función panic. En el resultado de este programa, primero vemos que la función diferida se ejecuta e imprime su mensaje. Después de esto, se encuentra el panic que generamos en main.

      Las funciones diferidas proporcionan protección contra la naturaleza sorprendente de los panics. En las funciones diferidas, Go también nos brinda la oportunidad de impedir que un panic cierre nuestro programa Go usando otra función builtin.

      Manejar los panics

      Los panics tienen un único mecanismo de recuperación: la función builtin recover. Esta función le permite interceptar un panic en su camino a la pila de invocación y evitar que cierre de forma inesperada su programa. Sus reglas de uso son estrictas, pero puede ser muy valiosa en una aplicación de producción.

      Ya que es parte del paquete builtin, recover puede invocarse sin importar paquetes adicionales:

      package main
      
      import (
          "fmt"
          "log"
      )
      
      func main() {
          divideByZero()
          fmt.Println("we survived dividing by zero!")
      
      }
      
      func divideByZero() {
          defer func() {
              if err := recover(); err != nil {
                  log.Println("panic occurred:", err)
              }
          }()
          fmt.Println(divide(1, 0))
      }
      
      func divide(a, b int) int {
          return a / b
      }
      

      El resultado de este ejemplo será el siguiente:

      Output

      2009/11/10 23:00:00 panic occurred: runtime error: integer divide by zero we survived dividing by zero!

      Nuestra función main en este ejemplo invoca una función que definimos: divideByZero. En esta función, aplicamos defer a una invocación de una función anónima responsable de manejar los panics que puedan surgir mientras se ejecuta divideByZero. En esta función diferida anónima, invocamos la función builtin recover y asignamos a una variable el error que muestra. Si divideByZero produce panics, se establecerá este valor error; de lo contrario, será nil. Al comparar la variable err contra nil, podemos detectar si se produjo un panic y, en este caso, registraremos el panic usando la función log.PrintIn, como si fuese cualquier otro error.

      Después de esta función anónima diferida, invocamos otra función que definimos, divide, e intentamos imprimir sus resultados usando fmt.PrintIn. Los argumentos proporcionados harán que divide realice una división por cero, lo que producirá un panic.

      En el resultado de este ejemplo, primero vemos el mensaje de registro de la función anónima que recupera el panic, seguido del mensaje we survived dividing by zero!. Hicimos esto gracias a la función builtin recover e impedimos un panic catastrófico que cerraría nuestro programa de Go.

      El valor err mostrado por recover() es exactamente el valor que se proporcionó a la invocación de panic(). Por lo tanto, es fundamental asegurarse de que el valor err sea solo nil cuando no se produzca un panic.

      Detectar panics con recover

      La función recover depende del valor del error para determinar si un panic se produjo o no. Debido a que el argumento para la función panic es una interfaz vacía, puede ser cualquier tipo. El valor cero para cualquier tipo de interfaz, incluida la interfaz vacía, es nil. Se debe proceder con cuidado para evitar nil como argumento de panic, como se demuestra en este ejemplo:

      package main
      
      import (
          "fmt"
          "log"
      )
      
      func main() {
          divideByZero()
          fmt.Println("we survived dividing by zero!")
      
      }
      
      func divideByZero() {
          defer func() {
              if err := recover(); err != nil {
                  log.Println("panic occurred:", err)
              }
          }()
          fmt.Println(divide(1, 0))
      }
      
      func divide(a, b int) int {
          if b == 0 {
              panic(nil)
          }
          return a / b
      }
      
      

      Esto dará el siguiente resultado:

      Output

      we survived dividing by zero!

      Este ejemplo es idéntico al anterior, en el que se utiliza recover con algunas modificaciones. La función divide se modificó para comprobar si su divisor b es igual a 0. Si es así, generará un panic usando la función builtin panic con un argumento nil. El resultado, esta vez, no incluye el mensaje de log que muestra que se produjo un panic, aunque divide haya generado uno. Este comportamiento silencioso es el motivo por el cual es muy importante verificar que el argumento para la función builtin panic no sea nil.

      Conclusión

      Vimos varias formas en que se pueden producir panics en Go y la manera en que podemos recuperar el sistema cuando suceden usando la función builtin recover. Aunque es posible que no utilice panic, la recuperación adecuada ante panics es importante para que las aplicaciones de Go estén listas para la producción.

      Puede explorar toda nuestra serie Cómo escribir código en Go.



      Source link