One place for hosting & domains

      personalizados

      Criando erros personalizados em Go


      Introdução

      O Go oferece dois métodos para criar erros na biblioteca padrão, errors.New e fmt.Errorf. Ao comunicar informações de erros mais complicados aos seus usuários, ou para si mesmo – num momento futuro, por vezes esses dois mecanismos não são o suficiente para capturar e relatar adequadamente o que aconteceu. Para transmitir essa informação de erro mais complexo e obter mais funcionalidade, podemos implementar o tipo de interface da biblioteca padrão, error.

      A sintaxe para isso seria a seguinte:

      type error interface {
        Error() string
      }
      

      O pacote builtin define error como uma interface com um método único Error() que retorna uma mensagem de erro como uma string. Ao implementar esse método, podemos transformar qualquer tipo que definirmos em um erro nosso.

      Vamos tentar executar o exemplo a seguir para ver uma implementação da interface error:

      package main
      
      import (
          "fmt"
          "os"
      )
      
      type MyError struct{}
      
      func (m *MyError) Error() string {
          return "boom"
      }
      
      func sayHello() (string, error) {
          return "", &MyError{}
      }
      
      func main() {
          s, err := sayHello()
          if err != nil {
              fmt.Println("unexpected error: err:", err)
              os.Exit(1)
          }
          fmt.Println("The string:", s)
      }
      

      Veremos o seguinte resultado:

      Output

      unexpected error: err: boom exit status 1

      Aqui, criamos um novo tipo de struct vazio, MyError e definimos o método Error() nele. O método Error() retorna a string "boom".

      Dentro do main(), chamamos a função sayHello que retorna uma string vazia e uma nova instância do MyError. Como o sayHello​​​ sempre retornará um erro, a chamada fmt.Println dentro do corpo da instrução if no main() sempre irá ser executada. Então, usamos o fmt.Println para imprimir a curta string do prefixo "unexpected error:" junto com a instância do MyError mantida dentro da variável err.

      Note que não precisamos chamar diretamente o Error(), uma vez que o pacote fmt consegue detectar automaticamente que se trata de uma implementação de error. Ele chama o Error() de maneira transparente para obter a string "boom“ e a concatena com a string do prefixo "unexpected error: err:".

      Coletando informações detalhadas em um erro personalizado

      Às vezes, um erro personalizado é a maneira mais limpa de capturar informações detalhadas de erro. Por exemplo, vamos supor que queremos capturar o código de status de erros produzidos por um pedido do HTTP; execute o programa a seguir para ver uma implementação do error que nos permite capturar de modo correto tais informações:

      package main
      
      import (
          "errors"
          "fmt"
          "os"
      )
      
      type RequestError struct {
          StatusCode int
      
          Err error
      }
      
      func (r *RequestError) Error() string {
          return fmt.Sprintf("status %d: err %v", r.StatusCode, r.Err)
      }
      
      func doRequest() error {
          return &RequestError{
              StatusCode: 503,
              Err:        errors.New("unavailable"),
          }
      }
      
      func main() {
          err := doRequest()
          if err != nil {
              fmt.Println(err)
              os.Exit(1)
          }
          fmt.Println("success!")
      }
      

      Veremos o seguinte resultado:

      Output

      status 503: err unavailable exit status 1

      Neste exemplo, criamos uma nova instância do RequestError e fornecemos o código de status e um erro usando a função errors.New da biblioteca padrão. Então, imprimimos isso usando o fmt.Println como em exemplos anteriores.

      Dentro do método Error() do RequestError, usamos a função fmt.Sprintf para construir uma string usando as informações fornecidas quando o erro foi criado.

      Declarações de tipo e erros personalizados

      A interface error mostra somente um método, mas podemos precisar acessar os outros métodos de implementações de error para lidar com um erro de maneira correta. Por exemplo, podemos ter várias implementações personalizadas do error que são temporárias e podem ser repetidas—indicadas pela presença de um método Temporary().

      As interfaces fornecem uma visualização limitada do conjunto mais amplo de métodos fornecidos por tipos. Assim, devemos usar uma asserção de tipo para alterar os métodos que a visualização está exibindo ou removê-la totalmente.

      O exemplo a seguir amplia o RequestError mostrado anteriormente com um método Temporary() que indicará se os chamadores devem ou não realizar o pedido novamente:

      package main
      
      import (
          "errors"
          "fmt"
          "net/http"
          "os"
      )
      
      type RequestError struct {
          StatusCode int
      
          Err error
      }
      
      func (r *RequestError) Error() string {
          return r.Err.Error()
      }
      
      func (r *RequestError) Temporary() bool {
          return r.StatusCode == http.StatusServiceUnavailable // 503
      }
      
      func doRequest() error {
          return &RequestError{
              StatusCode: 503,
              Err:        errors.New("unavailable"),
          }
      }
      
      func main() {
          err := doRequest()
          if err != nil {
              fmt.Println(err)
              re, ok := err.(*RequestError)
              if ok {
                  if re.Temporary() {
                      fmt.Println("This request can be tried again")
                  } else {
                      fmt.Println("This request cannot be tried again")
                  }
              }
              os.Exit(1)
          }
      
          fmt.Println("success!")
      }
      

      Veremos o seguinte resultado:

      Output

      unavailable This request can be tried again exit status 1

      Dentro do main(), chamamos o doRequest() que retorna uma interface error para nós. Primeiro, imprimimos a mensagem de erro que o método Error() retornou. Em seguida, tentamos expor todos os métodos do RequestError usando a asserção de tipo re, ok := err.(​​ *RequestError)​​​. Se o tipo declarado foi bem sucedido, usamos o método Temporary() para ver se este erro é um erro temporário. Como o StatusCode definido pelo doRequest() é o 503, que corresponde ao http.StatusServiceUnavailable, ele retorna true e faz com que a mensagem "This request can be tried again" (Esta solicitação pode ser repetida) seja impressa. Na prática, faríamos outro pedido em vez de imprimir uma mensagem.

      Erros de empacotamento

      Normalmente, um erro será gerado a partir de algo fora do seu programa, como: um banco de dados, uma conexão de rede etc. As mensagens de erro fornecidas a partir desses erros não ajudam ninguém a encontrar a origem do erro. O uso de erros de empacotamento com informações extra no início de uma mensagem de erro forneceria o contexto necessário para uma depuração bem-sucedida.

      O exemplo a seguir demonstra como podemos anexar informações contextuais a um error – que, de outro modo, seria criptografado – retornado de alguma outra função:

      package main
      
      import (
          "errors"
          "fmt"
      )
      
      type WrappedError struct {
          Context string
          Err     error
      }
      
      func (w *WrappedError) Error() string {
          return fmt.Sprintf("%s: %v", w.Context, w.Err)
      }
      
      func Wrap(err error, info string) *WrappedError {
          return &WrappedError{
              Context: info,
              Err:     err,
          }
      }
      
      func main() {
          err := errors.New("boom!")
          err = Wrap(err, "main")
      
          fmt.Println(err)
      }
      

      Veremos o seguinte resultado:

      Output

      main: boom!

      WrappedError é uma struct com dois campos: uma mensagem de contexto em forma de string e um error sobre o qual o WrappedError fornece mais informações. Quando o método Error() for chamado, usaremos o fmt.Sprintf novamente para imprimir a mensagem de contexto e, em seguida, o error (fmt.Sprintf sabe chamar implicitamente o método Error() também).

      Dentro do main(), criamos um erro usando o errors.New e, em seguida, empacotamos aquele erro usando a função Wrap que definimos. Isso nos permite indicar que esse error foi gerado no "main". Além disso, já que o nosso WrappedError também é um error, podemos empacotar outros WrappedErrors — isso nos permitiria ver uma cadeia que que nos ajudaria a rastrear a fonte do erro. Com um pouco de ajuda da biblioteca padrão, podemos até incorporar traços de pilha completos em nossos erros.

      Conclusão

      Como a interface error é apenas um método único, vimos que temos grande flexibilidade na oferta de diferentes tipos de erros para situações diferentes. Isso pode abranger tudo – desde a comunicação de vários fragmentos de informação como parte de um erro até a implementação de uma retirada exponencial. Embora os mecanismos de gerenciamento de erros no Go, em princípio, possam parecer simplistas, podemos chegar a um gerenciamento bastante detalhado usando esses erros personalizados para lidar com situações comuns e incomuns.

      O Go tem outro mecanismo para comunicar comportamentos inesperados, o panics (pânicos). No nosso próximo artigo na série de tratamento de erros, vamos examinar o panics — o que são e como lidar com eles.



      Source link

      Crear errores personalizados en Go


      Introducción

      Go ofrece dos métodos para crear errores en la biblioteca estándar: errors.New y fmt.Errorf. Cuando comunica información de error más complicada a sus usuarios, o a usted mismo al realizar una depuración, a veces estos dos mecanismos no son suficientes para capturar e informar de manera adecuada lo que sucedió. Para expresar esta información de error más compleja, y ampliar la funcionalidad, podemos implementar el tipo de interfaz de biblioteca estándar: error.

      La sintaxis para esto sería la siguiente:

      type error interface {
        Error() string
      }
      

      El paquete builtin define error como una interfaz con un único método Error() que muestra un mensaje de error como una cadena. Al implementar este método, podemos transformar cualquier tipo que definamos en un error propio.

      Intentaremos ejecutar el siguiente ejemplo para ver una implementación de la interfaz error:

      package main
      
      import (
          "fmt"
          "os"
      )
      
      type MyError struct{}
      
      func (m *MyError) Error() string {
          return "boom"
      }
      
      func sayHello() (string, error) {
          return "", &MyError{}
      }
      
      func main() {
          s, err := sayHello()
          if err != nil {
              fmt.Println("unexpected error: err:", err)
              os.Exit(1)
          }
          fmt.Println("The string:", s)
      }
      

      Veremos el siguiente resultado:

      Output

      unexpected error: err: boom exit status 1

      Aquí, hemos creado un nuevo tipo de estructura vacía, MyError, y hemos definido el método Error() en ella. El método Error() muestra la cadena "boom".

      En main(), invocamos la función sayHello que muestra una cadena vacía y una nueva instancia de MyError. Ya que sayHello siempre mostrará un error, la invocación fmt.PrintIn dentro del cuerpo de la declaración if en main() siempre se ejecutará. Utilizaremos fmt.PrintIn para imprimir la cadena de prefijo corto "unexpected error:" junto con la instancia de MyError que está en la variable err.

      Observe que no tenemos que invocar directamente Error(), ya que el paquete fmt puede detectar automáticamente que esta es una implementación de error. Invoca Error() de forma transparente para obtener la cadena "boom" y la concatena con la cadena de prefijo "unexpected error: err:".

      Recopilar información detallada en un error personalizado

      A veces, un error personalizado es la alternativa más prolija para capturar información detallada de un error. Por ejemplo, supongamos que queremos capturar el código de estado de los errores producidos por una solicitud HTTP. Ejecute el siguiente programa para ver una implementación de error que nos permita capturar de forma prolija esa información:

      package main
      
      import (
          "errors"
          "fmt"
          "os"
      )
      
      type RequestError struct {
          StatusCode int
      
          Err error
      }
      
      func (r *RequestError) Error() string {
          return fmt.Sprintf("status %d: err %v", r.StatusCode, r.Err)
      }
      
      func doRequest() error {
          return &RequestError{
              StatusCode: 503,
              Err:        errors.New("unavailable"),
          }
      }
      
      func main() {
          err := doRequest()
          if err != nil {
              fmt.Println(err)
              os.Exit(1)
          }
          fmt.Println("success!")
      }
      

      Verá el siguiente resultado:

      Output

      status 503: err unavailable exit status 1

      En este ejemplo, creamos una nueva instancia de RequestError y proporcionamos el código de estado y un error usando la función errors.New de la biblioteca estándar. A continuación, imprimimos esto usando fmt.PrintIn como en los ejemplos anteriores.

      En el método Error() de RequestError, usamos la función fmt.Sprintf para construir una cadena empleando la información proporcionada cuando se creó el error.

      Aserciones de tipo y errores personalizados

      La interfaz error expone solo un método, pero es posible que necesitemos acceder a otros métodos de implementaciones de error para gestionar un error de forma correcta. Por ejemplo, es posible que tengamos varias implementaciones personalizadas de error que sean temporales y puedan probarse nuevamente, denotadas por la presencia de un método Temporary().

      Las interfaces proporcionan una vista reducida del conjunto, más amplio, de métodos proporcionados por los tipos, de modo que debemos usar una_ aserción de tipo_ para cambiar los métodos que la vista muestra o para eliminarla completamente.

      El siguiente ejemplo aumenta el RequestError previamente mostrado para tener un método Temporary() que indicará si quienes realizan la invocación deberian intentar realizar nuevamente la solicitud o no:

      package main
      
      import (
          "errors"
          "fmt"
          "net/http"
          "os"
      )
      
      type RequestError struct {
          StatusCode int
      
          Err error
      }
      
      func (r *RequestError) Error() string {
          return r.Err.Error()
      }
      
      func (r *RequestError) Temporary() bool {
          return r.StatusCode == http.StatusServiceUnavailable // 503
      }
      
      func doRequest() error {
          return &RequestError{
              StatusCode: 503,
              Err:        errors.New("unavailable"),
          }
      }
      
      func main() {
          err := doRequest()
          if err != nil {
              fmt.Println(err)
              re, ok := err.(*RequestError)
              if ok {
                  if re.Temporary() {
                      fmt.Println("This request can be tried again")
                  } else {
                      fmt.Println("This request cannot be tried again")
                  }
              }
              os.Exit(1)
          }
      
          fmt.Println("success!")
      }
      

      Verá el siguiente resultado:

      Output

      unavailable This request can be tried again exit status 1

      En main(), invocamos doRequest() que muestra una interfaz error. Primero, imprimimos el mensaje de error mostrado por el método Error(). A continuación, intentamos exponer todos los métodos de RequestError usando la afirmación de tipo re, ok := err.( *RequestError). Si la aserción de tipo se realizó correctamente, usaremos el método Temporary() para ver si este error es temporal. Debido a que el StatusCode establecido por doRequest() es 503, que coincide con http.StatusServiceUnavailable, con esto se muestra true y se imprime "This request can be tried again". En la práctica, en vez de eso, realizaríamos otra solicitud en vez de imprimir un mensaje.

      Ajustar errores

      Comúnmente, un error se generará a partir de algo externo a su programa, como una base de datos y una conexión de red, entre otros ejemplos. Los mensajes de error proporcionados a partir de estos errores no ayudan a encontrar el origen del error. Ajustar los errores con información adicional al principio de un mensaje de error proporcionará el contexto necesario para realizar correctamente la depuración.

      En el siguiente ejemplo, se demuestra la forma en que podemos añadir información contextual a un error, mostrado por alguna otra función, que de otra forma sería enigmático.

      package main
      
      import (
          "errors"
          "fmt"
      )
      
      type WrappedError struct {
          Context string
          Err     error
      }
      
      func (w *WrappedError) Error() string {
          return fmt.Sprintf("%s: %v", w.Context, w.Err)
      }
      
      func Wrap(err error, info string) *WrappedError {
          return &WrappedError{
              Context: info,
              Err:     err,
          }
      }
      
      func main() {
          err := errors.New("boom!")
          err = Wrap(err, "main")
      
          fmt.Println(err)
      }
      

      Verá el siguiente resultado:

      Output

      main: boom!

      WrappedError es una estructura con dos campos: un mensaje de contexto como una string y un error sobre el cual WrappedError proporciona más información. Cuando se invoca el método Error(), de nuevo usamos fmt.Sprintf para imprimir el mensaje de contexto; luego el error (fmt.Sprintf sabe cómo invocar de forma implícita el método Error() también).

      En main(), creamos un error usando errors.New y luego ajustamos ese error usando la función Wrap que definimos. Esto nos permite indicar que este error se generó en "main". Además, ya que nuestro WrappedError es también un error, podríamos ajustar otro WrappedError; esto nos permitiría ver una cadena para poder rastrear el origen del error. Con algo de ayuda de la biblioteca estándar, podemos incluso integrar seguimientos de pila completos en nuestros errores.

      Conclusión

      Debido a que la interfaz error es solo un método único, hemos visto que disponemos de una gran flexibilidad para proporcionar diferentes tipos de error para diferentes situaciones. Esto puede abarcar todo, desde la comunicación de varios fragmentos de información como parte de un error hasta la implementación de un retroceso exponencial. Aunque los mecanismos de gestión de errores en Go pueden parecer, a primera vista, simplistas, podemos lograr un manejo bastante bueno usando estos errores personalizados para gestionar situaciones comunes y atípicas.

      Go tiene otro mecanismo para comunicar el comportamiento inesperado: los panics. En nuestro siguiente artículo de la serie de gestión de errores, veremos los panics, qué son y la forma gestionarlos.



      Source link