One place for hosting & domains

      Panics

      Lidando com a função Panics em Go


      Introdução

      Os erros que um programa encontra se enquadram em duas grandes categorias: a dos erros que o programador previu e a dos que ele não previu. A interface de error – que abordamos nos dois artigos anteriores sobre Como lidar com erros – trata, em grande parte, dos erros esperados, à medida que escrevemos programas em Go. A interface de error nos permite reconhecer a rara possibilidade de um erro ocorrer a partir das chamadas de função, de modo que possamos responder adequadamente nessas situações.

      Panics se enquadra na segunda categoria de erros, os quais não são previstos pelo programador. Esses erros imprevistos levam um programa a fechar espontaneamente e a sair do programa Go em execução. Com frequência, os erros comuns são os responsáveis pela criação de panics. Neste tutorial, examinaremos algumas maneiras como operações comuns podem produzir panics em Go, bem como veremos maneiras de evitar esses panics. Também usaremos as instruções defer junto com a função recover para captar panics, antes que tenham a chance de encerrar inesperadamente nossos programas do Go em execução.

      Entendendo a função Panics

      Existem certas operações em Go que retornam panics automaticamente e interrompem o programa. As operações comuns incluem indexar uma matriz para além de sua capacidade, executar as afirmações de tipo, chamar métodos em ponteiros nil, utilizar incorretamente exclusões mútuas e tentar trabalhar com canais fechados. A maioria destas situações resultam de erros cometidos durante a programação, em que o compilador não tem a capacidade de detectar enquanto compila o seu programa.

      Uma vez que os panics incluem detalhes úteis para resolver um problema, normalmente, os desenvolvedores os utilizam como uma indicação de que cometeram um erro durante o desenvolvimento de um programa.

      Panics fora dos limites

      Ao tentar acessar um índice que vai além do tamanho de uma fatia ou da capacidade de uma matriz, o tempo de execução do Go irá gerar um panic.

      O exemplo a seguir comete o erro comum de tentar acessar o último elemento de uma fatia usando o tamanho da fatia retornada pela função integrada len. Tente executar este código para ver por que isso pode produzir um panic:

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

      Isso terá o seguinte resultado:

      Output

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

      O nome do resultado do panic fornece uma dica: panic: runtime error: index out of range (pânico: erro de tempo de execução: índice fora do intervalo). Criamos uma fatia com três criaturas marinhas. Em seguida, tentamos obter o último elemento da fatia, indexando aquela fatia com o tamanho própria fatia – com a função integrada len. Lembre-se que fatias e matrizes são baseadas em zero; portanto, o primeiro elemento é zero e o último elemento nessa fatia está no índice 2. Considerando que tentamos acessar a fatia no terceiro índice, 3, não há nehum elemento na fatia para retornar porque ele está fora dos limites da fatia. Assim, ao tempo de execução não resta outra opção senão a de encerrar e sair, uma vez que pedimos a ele que fizesse algo impossível. Além disso, durante a compilação, o Go não tem como provar que esse código tentará fazer isso e, consequentemente, o compilador não consegue detectar a ocorrência.

      Note também que o código subsequente não foi executado. Isso acontece porque um panic é um evento que impede completamente a execução do seu programa Go. A mensagem produzida contém vários fragmentos de informações úteis para diagnosticar a causa do panic.

      Anatomia de um panic

      Os panics são compostos por uma mensagem que indica a causa do panic e um rastreamento de pilha que ajuda a localizar onde, em seu código, o panic foi produzido.

      A primeira parte de qualquer panic é a mensagem. Ela começará sempre com a string panic:, seguida de uma string que varia de acordo com a causa do panic. O panic do exercício anterior tem a mensagem:

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

      A string runtime error: depois do prefixo panic: nos diz que o panic foi gerado pelo tempo de execução da linguagem. Esse panic nos diz que tentamos usar um índice [3] que estava fora do alcance do comprimento da fatia 3.

      Após essa mensagem está o rastreamento de pilha. Os rastreamentos de pilha formam um mapa que podemos seguir para localizar exatamente qual linha de código estava executando quando o panic foi gerado e como aquele código foi invocado pelo código anterior.

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

      O rastreamento de pilha do exemplo anterior mostra que nosso programa gerou o panic a partir do arquivo /tmp/sandbox879828148/prog.go,​​​ na linha de número 13. Além disso, ele nos diz que esse panic foi gerado na função main() do pacote main.

      O rastreamento de pilha é dividido em blocos separados — sendo um para cada goroutine de seu programa. Toda execução do programa Go é realizada por uma ou mais goroutines que podem, cada qual – de modo simultâneo e independente, executar partes de seu código Go. Cada block inicia com o cabeçalho goroutine X [state]: O cabeçalho dá o número da ID da goroutine junto com o estado em que ela estava quando o panic ocorreu. Após o cabeçalho, o rastreamento de pilha mostra a função que o programa estava executando quando o panic aconteceu, junto com o nome do arquivo e número da linha onde a função foi executada.

      O panic no exemplo anterior foi gerado por um acesso fora dos limites de uma fatia. Os panics também podem ser gerados quando os métodos são chamados nos ponteiros que não foram definidos.

      Receptores nulos

      A linguagem de programação Go tem ponteiros para se referir à instância específica de algum tipo existente na memória do computador em tempo de execução. Os ponteiros podem assumir o valor nil, o que indica que não estão apontando para nada. Quando tentarmos chamar métodos em um ponteiro que é nil, o tempo de execução do Go irá gerar um panic. De igual modo, as variáveis que são tipos de interface também produzirão panics quando os métodos forem chamados neles. Para ver os panics gerados nesses casos, teste o seguinte exemplo:

      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()
      }
      

      Os panics produzidos se parecerão com isto:

      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

      Neste exemplo, definimos um struct chamado Shark. O Shark tem um método definido em seu ponteiro receptor chamado SayHello, que irá imprimir uma saudação para a saída padrão quando chamado. Dentro do corpo da nossa função main, criamos uma nova instância desse struct Shark e pedimos um ponteiro a ela, usando o operador &. Esse ponteiro está atribuído à variável s. Em seguida, vamos reatribuir a variável s para o valor nil com a instrução s = nil. Por fim, tentamos chamar o método SayHello na variável s. Em vez de receber uma mensagem amigável do Sammy, recebemos um panic de que tentamos acessar um endereço de memória inválido. Como a variável s é nil, quando a função SayHello é chamada, ela tenta acessar o campo Name no tipo *Shark. Como se trata de um ponteiro receptor e, neste caso, de um receptor que é nil, ele entra em pânico, uma vez que não consegue desreferenciar um ponteiro nil.

      A despeito de termos definido s para nil de maneira explícita neste exemplo, na prática isso ocorre não ocorre de modo tão claro. Ao ver panics envolvendo nil pointer dereference, certifique-se de ter atribuído devidamente quaisquer variáveis de ponteiros que possa ter criado.

      Panics gerados a partir de ponteiros nulos e de acessos fora dos limites são dois panics de ocorrência comum, sendo gerados pelo tempo de execução. Também é possível gerar um panic manualmente, usando uma função integrada.

      Usando o função integrada panic

      Também podemos gerar nossos próprios panics usando a função integrada panic. Essa função utiliza uma string única como argumento, que é a mensagem que o panic produzirá. Normalmente, essa mensagem resulta menos prolixa do que reescrever o código para retornar um erro. Além disso, podemos usar isso dentro dos nossos próprios pacotes para indicar aos desenvolvedores que eles podem ter cometido um erro ao usar o código do nosso pacote. Sempre que possível, o melhor a se fazer é tentar retornar valores de error para os consumidores de nossos pacotes.

      Execute este código para ver um panic gerado a partir de uma função chamada de outra função:

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

      O resultado do panic produzido fica parecido com este:

      Output

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

      Aqui, definimos uma função foo que chama o panic integrado com a string "oh no!". Essa função é chamada por nossa função main. Observe que o resultado tem a mensagem panic: oh no! e o rastreamento de pilha mostra uma goroutine única com duas linhas no rastreamento de pilha: uma para a função main() e outra para nossa função foo().

      Vimos que os panics parecem encerrar nosso programa no local em que foram gerados. Isso pode criar problemas quando houver recursos abertos que precisam ser fechados de maneira adequada. O Go fornece um mecanismo para sempre executar alguns códigos, mesmo na presença de um panic.

      Funções adiadas

      Seu programa pode ter recursos que ele deve limpar de maneira adequada, mesmo enquanto um panic estiver sendo processado em tempo de execução. O Go permite que você adie a execução de uma chamada de função até que sua chamada tenha concluído a execução. As funções adiadas são executadas mesmo na presença de um panic. Elas são usadas como um mecanismo de segurança para proteger o programa da natureza caótica dos panics. As funções são adiadas quando as chamamos como de costume e depois fazemos a prefixação da instrução toda, usando a palavra-chave defer, como em defer sayHello(). Execute este exemplo para ver como uma mensagem pode ser impressa, ainda que um panic tenha sido produzido:

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

      O resultado produzido a partir desse exemplo se parecerá com:

      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

      Dentro da função main desse exemplo, fazemos primeiro o defer (adiamos) de uma chamada para uma função anônima que imprime a mensagem "hello from the deferred function!". Na sequência, a função main produz imediatamente um panic usando a função panic. No resultado desse programa, vemos primeiro que a função adiada é executada e imprime sua mensagem. Depois disso, vemos o panic que geramos na função main.

      As funções adiadas proporcionam proteção contra a natureza surpreendente dos panics. Dentro das funções adiadas, o linguagem de programação Go também nos dá a oportunidade de impedir um panic de encerrar nosso programa Go, usando outra função integrada.

      Os panics possuem um mecanismo de recuperação único — a função integrada recover. Essa função permite que você intercepte um panic em seu caminho até a pilha de chamadas e impeça-o de encerrar inesperadamente o seu programa. Ela possui regras estritas de uso, mas pode ser algo inestimável em um aplicativo de produção.

      Como ela faz parte do pacote builtin, a função recover pode ser chamada sem importar nenhum pacote adicional:

      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
      }
      

      Este exemplo produzirá o seguinte:

      Output

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

      Nesse exemplo, nossa função main chama uma função que definimos, a divideByZero. Dentro dessa função, usamos a função defer para adiar uma chamada para uma função anônima – responsável por lidar com quaisquer panics que possam surgir durante o execução da função divideByZero. Dentro dessa função anônima adiada, chamamos a função integrada recover (recuperar) e atribuímos o erro que ela retorna para uma variável. Se o divideByZero entrar em pânico, esse valor de error será definido, caso contrário, ele será nil (nulo ou inválido). Ao comparar a variável err com a nil, podemos detectar se um panic ocorreu, e neste caso, registramos o evento de panic usando a função log.Println – como se fosse qualquer outro error.

      Depois dessa função anônima adiada, chamados outra função que definimos – a divide – e tentamos imprimir seus resultados usando fmt.Println. Os argumentos fornecidos farão a divide executar uma divisão por zero, o que produzirá um panic.

      No resultado para esse exemplo, vemos primeiro a mensagem de registro da função anônima que recupera o panic, seguida da mensagem we survived dividing by zero! (sobrevivemos à divisão por zero!). Nós fizemos isso, graças à função integrada recover que impediu que um panic catastrófico fechasse nosso programa em Go.

      O valor de err retornado do recover() é exatamente o valor que foi fornecido à chamada para o panic(). Assim, é crucial garantir que o valor de err somente seja nil na ausência de um panic.

      A função recover depende do valor do erro para fazer determinações quanto a se um panic ocorreu ou não. Como o argumento para a função panic é uma interface vazia, ele pode ser de qualquer tipo. O valor zero para qualquer tipo de interface, incluindo a interface vazia, é nil. Tome cuidado para evitar o nil como um argumento para panic, como mostrado por este exemplo:

      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
      }
      
      

      Isso resultará em:

      Output

      we survived dividing by zero!

      Esse exemplo é o mesmo que o exemplo anterior, que envolve o recover com algumas pequenas alterações. A função divide foi alterada para verificar se seu divisor b é igual a 0. Caso seja, ele irá gerar um panic usando a função panic integrada com um argumento de nil. O resultado, dessa vez, não inclui a mensagem de registro mostrando que um panic ocorreu mesmo que um tenha sido criado pelo divide. Esse comportamento silencioso acontece porque é muito importante garantir que o argumento para a função integrada panic não seja nil.

      Conclusão

      Vimos várias maneiras para a criação de panics em Go e como eles podem ser recuperados usando a função recover integrada. Ainda que você não faça, necessariamente, uso da função de panic, propriamente dita, a recuperação adequada dos eventos de panics é um passo importante para deixar os aplicativos em Go prontos para a produção.

      Você também pode explorar nossa série de artigos sobre Como codificar em Go.



      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

      Handling Panics in Go


      Introduction

      Errors that a program encounters fall into two broad categories: those the programmer has anticipated and those the programmer has not. The error interface that we have covered in our previous two articles on error handling largely deal with errors that we expect as we are writing Go programs. The error interface even allows us to acknowledge the rare possibility of an error occurring from function calls, so we can respond appropriately in those situations.

      Panics fall into the second category of errors, which are unanticipated by the programmer. These unforeseen errors lead a program to spontaneously terminate and exit the running Go program. Common mistakes are often responsible for creating panics. In this tutorial, we’ll examine a few ways that common operations can produce panics in Go, and we’ll also see ways to avoid those panics. We’ll also use defer statements along with the recover function to capture panics before they have a chance to unexpectedly terminate our running Go programs.

      Understanding Panics

      There are certain operations in Go that automatically return panics and stop the program. Common operations include indexing an array beyond its capacity, performing type assertions, calling methods on nil pointers, incorrectly using mutexes, and attempting to work with closed channels. Most of these situations result from mistakes made while programming that the compiler has no ability to detect while compiling your program.

      Since panics include detail that is useful for resolving an issue, developers commonly use panics as an indication that they have made a mistake during a program’s development.

      Out of Bounds Panics

      When you attempt to access an index beyond the length of a slice or the capacity of an array, the Go runtime will generate a panic.

      The following example makes the common mistake of attempting to access the last element of a slice using the length of the slice returned by the len builtin. Try running this code to see why this might produce a panic:

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

      This will have the following output:

      Output

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

      The name of the panic’s output provides a hint: panic: runtime error: index out of range. We created a slice with three sea creatures. We then tried to get the last element of the slice by indexing that slice with the length of the slice using the len builtin function. Remember that slices and arrays are zero-based; so the first element is zero and the last element in this slice is at index 2. Since we try to access the slice at the third index, 3, there is no element in the slice to return because it is beyond the bounds of the slice. The runtime has no option but to terminate and exit since we have asked it to do something impossible. Go also can’t prove during compilation that this code will try to do this, so the compiler cannot catch this.

      Notice also that the subsequent code did not run. This is because a panic is an event that completely halts the execution of your Go program. The message produced contains multiple pieces of information helpful for diagnosing the cause of the panic.

      Anatomy of a Panic

      Panics are composed of a message indicating the cause of the panic and a stack trace that helps you locate where in your code the panic was produced.

      The first part of any panic is the message. It will always begin with the string panic:, which will be followed with a string that varies depending on the cause of the panic. The panic from the previous exercise has the message:

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

      The string runtime error: following the panic: prefix tells us that the panic was generated by the language runtime. This panic is telling us that we attempted to use an index [3] that was out of range of the slice’s length 3.

      Following this message is the stack trace. Stack traces form a map that we can follow to locate exactly what line of code was executing when the panic was generated, and how that code was invoked by earlier code.

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

      This stack trace, from the previous example, shows that our program generated the panic from the file /tmp/sandbox879828148/prog.go at line number 13. It also tells us that this panic was generated in the main() function from the main package.

      The stack trace is broken into separate blocks—one for each goroutine in your program. Every Go program’s execution is accomplished by one or more goroutines that can each independently and simultaneously execute parts of your Go code. Each block begins with the header goroutine X [state]:. The header gives the ID number of the goroutine along with the state that it was in when the panic occurred. After the header, the stack trace shows the function that the program was executing when the panic happened, along with the filename and line number where the function executed.

      The panic in the previous example was generated by an out-of-bounds access to a slice. Panics can also be generated when methods are called on pointers that are unset.

      Nil Receivers

      The Go programming language has pointers to refer to a specific instance of some type existing in the computer’s memory at runtime. Pointers can assume the value nil indicating that they are not pointing at anything. When we attempt to call methods on a pointer that is nil, the Go runtime will generate a panic. Similarly, variables that are interface types will also produce panics when methods are called on them. To see the panics generated in these cases, try the following example:

      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()
      }
      

      The panics produced will look like this:

      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

      In this example, we defined a struct called Shark. Shark has one method defined on its pointer receiver called SayHello that will print a greeting to standard out when called. Within the body of our main function, we create a new instance of this Shark struct and request a pointer to it using the & operator. This pointer is assigned to the s variable. We then reassign the s variable to the value nil with the statement s = nil. Finally we attempt to call the SayHello method on the variable s. Instead of receiving a friendly message from Sammy, we receive a panic that we have attempted to access an invalid memory address—this is because we set the s variable to nil.

      While we have set s to nil explicitly in this example, in practice this happens less obviously. When you see panics involving nil pointer dereference, be sure that you have properly assigned any pointer variables that you may have created.

      Panics generated from nil pointers and out-of-bounds accesses are two commonly occurring panics generated by the runtime. It is also possible to manually generate a panic using a builtin function.

      Using the panic Builtin Function

      We can also generate panics of our own using the panic built-in function. It takes a single string as an argument, which is the message the panic will produce. Typically this message is less verbose than rewriting our code to return an error. Furthermore, we can use this within our own packages to indicate to developers that they may have made a mistake when using our package’s code. Whenever possible, best practice is to try to return error values to consumers of our package.

      Run this code to see a panic generated from a function called from another function:

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

      The panic output produced looks like:

      Output

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

      Here we define a function foo that calls the panic builtin with the string "oh no!". This function is called by our main function. Notice how the output has the message panic: oh no! and the stack trace shows a single goroutine with two lines in the stack trace: one for the main() function and one for our foo() function.

      We’ve seen that panics appear to terminate our program where they are generated. This can create problems when there are open resources that need to be properly closed. Go provides a mechanism to execute some code always, even in the presence of a panic.

      Deferred Functions

      Your program may have resources that it must clean up properly, even while a panic is being processed by the runtime. Go allows you to defer the execution of a function call until its calling function has completed execution. Deferred functions run even in the presence of a panic, and are used as a safety mechanism to guard against the chaotic nature of panics. Functions are deferred by calling them as usual, then prefixing the entire statement with the defer keyword, as in defer sayHello(). Run this example to see how a message can be printed even though a panic was produced:

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

      The output produced from this example will look like:

      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

      Within the main function of this example, we first defer a call to an anonymous function that prints the message "hello from the deferred function!". The main function then immediately produces a panic using the panic function. In the output from this program, we first see that the deferred function is executed and prints its message. Following this is the panic we generated in main.

      Deferred functions provide protection against the surprising nature of panics. Within deferred functions, Go also provides us the opportunity to stop a panic from terminating our Go program using another built-in function.

      Handling Panics

      Panics have a single recovery mechanism—the recover builtin function. This function allows you to intercept a panic on its way up through the call stack and prevent it from unexpectedly terminating your program. It has strict rules for its use, but can be invaluable in a production application.

      Since it is part of the builtin package, recover can be called without importing any additional packages:

      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
      }
      

      This example will output:

      Output

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

      Our main function in this example calls a function we define, divideByZero. Within this function, we defer a call to an anonymous function responsible for dealing with any panics that may arise while executing divideByZero. Within this deferred anonymous function, we call the recover builtin function and assign the error it returns to a variable. If divideByZero is panicking, this error value will be set, otherwise it will be nil. By comparing the err variable against nil, we can detect if a panic occurred, and in this case we log the panic using the log.Println function, as though it were any other error.

      Following this deferred anonymous function, we call another function that we defined, divide, and attempt to print its results using fmt.Println. The arguments provided will cause divide to perform a division by zero, which will produce a panic.

      In the output to this example, we first see the log message from the anonymous function that recovers the panic, followed by the message we survived dividing by zero!. We have indeed done this, thanks to the recover builtin function stopping an otherwise catastrophic panic that would terminate our Go program.

      The err value returned from recover() is exactly the value that was provided to the call to panic(). It’s therefore critical to ensure that the err value is only nil when a panic has not occurred.

      Detecting Panics with recover

      The recover function relies on the value of the error to make determinations as to whether a panic occurred or not. Since the argument to the panic function is an empty interface, it can be any type. The zero value for any interface type, including the empty interface, is nil. Care must be taken to avoid nil as an argument to panic as demonstrated by this example:

      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
      }
      
      

      This will output:

      Output

      we survived dividing by zero!

      This example is the same as the previous example involving recover with some slight modifications. The divide function has been altered to check if its divisor, b, is equal to 0. If it is, it will generate a panic using the panic builtin with an argument of nil. The output, this time, does not include the log message showing that a panic occurred even though one was created by divide. This silent behavior is why it is very important to ensure that the argument to the panic builtin function is not nil.

      Conclusion

      We have seen a number of ways that panics can be created in Go and how they can be recovered from using the recover builtin. While you may not necessarily use panic yourself, proper recovery from panics is an important step of making Go applications production-ready.

      You can also explore our entire How To Code in Go series.



      Source link