Base de Conhecimento
Criando erros personalizados no Go Imprimir este Artigo
Introdução
Go fornece dois métodos para criar erros na biblioteca padrão errors.New
efmt.Errorf
. Ao comunicar informações de erro mais complicadas aos seus usuários ou ao seu futuro quando depurar, algumas vezes esses dois mecanismos não são suficientes para capturar e relatar adequadamente o que aconteceu. Para transmitir essas informações de erro mais complexas e obter mais funcionalidades, podemos implementar o tipo de interface de biblioteca padrão error
.
A sintaxe para isso seria a seguinte:
type error interface {
Error() string
}
O builtin
pacote define error
como uma interface com um único Error()
método que retorna uma mensagem de erro como uma sequência. Ao implementar esse método, podemos transformar qualquer tipo que definimos em um erro próprio.
Vamos tentar executar o exemplo a seguir para ver uma implementação da error
interface:
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 a seguinte saída:
unexpected error: err: boom
exit status 1
Aqui, criamos um novo tipo de estrutura vazio MyError
, e definimos o Error()
método nele. O Error()
método retorna a string "boom"
.
Dentro main()
, chamamos a função sayHello
que retorna uma string vazia e uma nova instância de MyError
. Como sayHello
sempre retornará um erro, a fmt.Println
invocação no corpo da instrução if main()
sempre será executada. Em seguida, usamos fmt.Println
para imprimir a sequência de prefixos curta, "unexpected error:"
juntamente com a instância de MyError
realizada na err
variável.
Observe que não precisamos ligar diretamente Error()
, pois o fmt
pacote pode detectar automaticamente que essa é uma implementação do error
. Ele chama de forma Error()
transparente para obter a sequência "boom"
e concatena-a com a sequência de 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 sobre erros. Por exemplo, digamos que queremos capturar o código de status para erros produzidos por uma solicitação HTTP; execute o programa a seguir para ver uma implementação error
que permite capturar essas informações corretamente:
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 a seguinte saída:
status 503: err unavailable
exit status 1
Neste exemplo, criamos uma nova instância RequestError
e fornecemos o código de status e um erro usando a errors.New
função da biblioteca padrão. Em seguida, imprimimos isso usando fmt.Println
como nos exemplos anteriores.
Dentro do Error()
método de RequestError
, usamos a fmt.Sprintf
função para construir uma string usando as informações fornecidas quando o erro foi criado.
Declarações de tipo e erros personalizados
A error
interface expõe apenas um método, mas podemos precisar acessar os outros métodos de error
implementações para manipular um erro corretamente. Por exemplo, podemos ter várias implementações personalizadas error
que são temporárias e podem ser repetidas - denotadas pela presença de um Temporary()
método.
As interfaces fornecem uma visão estreita do conjunto mais amplo de métodos fornecidos pelos tipos, portanto, devemos usar uma asserção de tipo para alterar os métodos que a exibição está exibindo ou para removê-la completamente.
O exemplo a seguir aumenta o RequestError
mostrado anteriormente para ter um Temporary()
método que indica se os chamadores devem ou não tentar novamente a solicitação:
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 a seguinte saída:
unavailable
This request can be tried again
exit status 1
Dentro main()
, chamamos doRequest()
que retorna uma error
interface para nós. Primeiro, imprimimos a mensagem de erro retornada pelo Error()
método Em seguida, tentamos expor todos os métodos RequestError
usando a asserção de tipo re, ok := err.(*RequestError)
. Se a afirmação de tipo for bem-sucedida, usamos o Temporary()
método para verificar se esse erro é temporário. Como o StatusCode
definido por doRequest()
é 503
, que corresponde http.StatusServiceUnavailable
, isso retorna true
e faz "This request can be tried again"
com que seja impresso. Na prática, faríamos outra solicitação em vez de imprimir uma mensagem.
Erros de agrupamento
Geralmente, 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 com esses erros não ajudam ninguém a encontrar a origem do erro. O agrupamento de erros com informações extras no início de uma mensagem de erro forneceria algum contexto necessário para a depuração bem-sucedida.
O exemplo a seguir demonstra como podemos anexar algumas informações contextuais a um enigmático error
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 a seguinte saída:
main: boom!
WrappedError
é uma estrutura com dois campos: uma mensagem de contexto como a string
e uma sobre a error
qual isso WrappedError
fornece mais informações. Quando o Error()
método é chamado, usamos novamente fmt.Sprintf
para imprimir a mensagem de contexto e, em seguida, o error
( fmt.Sprintf
sabe também implicitamente chamar o Error()
método).
Dentro main()
, criamos um erro usando errors.New
e, em seguida, envolvemos esse erro usando a Wrap
função que definimos. Isso nos permite indicar que isso error
foi gerado em "main"
. Além disso, como o nosso WrappedError
também é um error
, poderíamos agrupar outros WrappedError
s - isso nos permitiria ver uma cadeia para nos ajudar a rastrear a origem do erro. Com uma pequena ajuda da biblioteca padrão, podemos até incorporar rastreamentos de pilha completos em nossos erros.
Conclusão
Como a error
interface é apenas um método único, vimos que temos grande flexibilidade ao fornecer diferentes tipos de erros para diferentes situações. Isso pode abranger tudo, desde a comunicação de várias informações como parte de um erro até a implementação de retirada exponencial . Embora os mecanismos de manipulação de erros no Go possam parecer simplistas, podemos obter uma manipulação bastante rica usando esses erros personalizados para lidar com situações comuns e incomuns.
O Go possui outro mecanismo para comunicar comportamentos inesperados, o pânico. No próximo artigo da série sobre tratamento de erros, examinaremos o pânico - o que são e como lidar com eles.
Esta resposta lhe foi útil?
Veja também
Introdução Uma grande quantidade de dados gerados hoje não é estruturada , o que requer...
Introdução O MTA-STS (Security Strict Transport Security) do Mail Transport Agent é um novo...
Introdução Uma função variável é uma função que aceita zero, um ou mais valores como um único...
Introdução Python é uma linguagem de programação versátil que pode ser usada para muitos...
Introdução Instruções condicionais fazem parte de toda linguagem de programação. Com instruções...