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.Newefmt.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
}

builtinpacote define errorcomo 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 errorinterface:

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:

Output
unexpected error: err: boom
exit status 1

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

Dentro main(), chamamos a função sayHelloque retorna uma string vazia e uma nova instância de MyErrorComo sayHellosempre retornará um erro, a fmt.Printlninvocação no corpo da instrução if main()sempre será executada. Em seguida, usamos fmt.Printlnpara imprimir a sequência de prefixos curta, "unexpected error:"juntamente com a instância de MyErrorrealizada na errvariável.

Observe que não precisamos ligar diretamente Error(), pois o fmtpacote pode detectar automaticamente que essa é uma implementação do errorEle 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 errorque 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:

Output
status 503: err unavailable
exit status 1

Neste exemplo, criamos uma nova instância RequestErrore fornecemos o código de status e um erro usando a errors.Newfunção da biblioteca padrão. Em seguida, imprimimos isso usando fmt.Printlncomo nos exemplos anteriores.

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

Declarações de tipo e erros personalizados

errorinterface expõe apenas um método, mas podemos precisar acessar os outros métodos de errorimplementações para manipular um erro corretamente. Por exemplo, podemos ter várias implementações personalizadas errorque 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 RequestErrormostrado 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:

Output
unavailable
This request can be tried again
exit status 1

Dentro main(), chamamos doRequest()que retorna uma errorinterface para nós. Primeiro, imprimimos a mensagem de erro retornada pelo Error()método Em seguida, tentamos expor todos os métodos RequestErrorusando 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 StatusCodedefinido por doRequest()é 503, que corresponde http.StatusServiceUnavailable, isso retorna truee 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 errorretornado 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:

Output
main: boom!

WrappedErroré uma estrutura com dois campos: uma mensagem de contexto como a stringe uma sobre a errorqual isso WrappedErrorfornece mais informações. Quando o Error()método é chamado, usamos novamente fmt.Sprintfpara imprimir a mensagem de contexto e, em seguida, o errorfmt.Sprintfsabe também implicitamente chamar o Error()método).

Dentro main(), criamos um erro usando errors.Newe, em seguida, envolvemos esse erro usando a Wrapfunção que definimos. Isso nos permite indicar que isso errorfoi gerado em "main"Além disso, como o nosso WrappedErrortambém é um error, poderíamos agrupar outros WrappedErrors - 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 errorinterface é 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

Como usar o Git para gerenciar seu projeto
Introdução O controle de versão não é apenas para código. É para qualquer coisa que você queira...
Como criar um aplicativo Ruby on Rails
Introdução O Rails é uma estrutura de aplicativos da Web escrita em Ruby . É necessária uma...
Como instalar o Python 3 e configurar um ambiente de programação local no macOS
Introdução Python é uma linguagem de programação versátil que pode ser usada para muitos...
Como definir e chamar funções no Go
Introdução Uma função é uma seção de código que, uma vez definida, pode ser reutilizada. As...
Como adicionar o Bootstrap a um aplicativo Ruby on Rails
Introdução Se você estiver desenvolvendo um aplicativo Ruby on Rails , pode estar interessado em...