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:
Outputunexpected 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:"
.
À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:
Outputstatus 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.
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:
Outputunavailable
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.
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:
Outputmain: 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 WrappedError
s — 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.
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.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!