Dicas e práticas sobre error handling em Go

Hoje vamos falar sobre um tópico crucial em Go, como lidar com erros. Em Go, erros são valores importantes e devem ser tratados com cuidado.

Definindo um error

Primeiramente, em Go, um erro é qualquer valor que implementa a interface error. Esta interface tem um único método: Error() string.

type MyError struct {
    Message string
    Code    int
}

func (e *MyError) Error() string {
    return fmt.Sprintf("error: %s, code: %d", e.Message, e.Code)
}

Embora não seja obrigatório, para facilitar a utilização desse tipo de erro que criamos, podemos escrever uma função New que preenche os atributos.

func New(msg string, code int) *MyError {
	return &MyError{ msg, code }
}

Outro ponto interessante, é que MyError pode ser utilizado como retorno em uma função que retorna um tipo error.

Retornando error

Uma prática comum é retornar o erro como o último valor de uma função. Isso permite verificar se uma operação foi bem-sucedida ou não. Por exemplo:

func Funcao() (int, error) {
    // lógica da função
}

Dentro dessa função, podemos retornar um erro gerado com errors.New, fmt.Errorf, a função New que criamos anteriormente, ou somente propagar algum erro gerado em alguma chamada interna da função.

O único problema de utilizar qualquer uma dessas 3 funções para retornar um erro, é que ele não será facilmente testável. Isso por que, para testá-lo, será necessário validar a mensagem retornada.

No entanto, para erros conhecidos, Go nos fornece outra abordagem, os chamados sentinel errors. Nessa abordagem, utilizamos erros pré-definidos que podem ser usados para comparação. Um exemplo desse tipo de erro é o io.EOF, que indica o final de um arquivo.

Embora essa abordagem tenha esse nome exclusivo, ela nada mais é do que armazenar os erros em uma variável fora da função. Para o tipo de erro que criamos no início, podemos ter algo como:

var (
	ErrNameRequired = New("O Campo nome é obrigatório", http.StatusBadRequest)
)

Tratando error

A chave é o tratamento de erros. Não ignore os erros. Sempre verifique se um erro ocorreu e trate-o de maneira apropriada. Isso pode significar logar o erro, retorná-lo, ou até mesmo parar a execução.

err := json.Unmarshal(data, &v)
if err != nil {
	// tratar o erro
}

Além dessa forma, Go 1.13 introduziu melhorias no manuseio de erros, como errors.As e errors.Is, facilitando a verificação e a manipulação de tipos específicos de erros.

person, err := CreateEntity(data)
if errors.Is(err, ErrNameRequired) {
	// faça alguma coisa
}

A diferença básica entre as duas funções é que, errors.Is verifica a igualdade de erros, incluindo erros encadeados, ou seja, é útil para comparar erros com valores de erro específicos (como os sentinel errors). Já errors.As é usado para verificar se um erro é de um tipo específico e, se for, fornece acesso à sua instância. Com errors.As, você pode extrair informações adicionais do erro, assumindo que ele é do tipo correto.

Em outras palavras, errors.Is é sobre comparar erros, enquanto errors.As é sobre transformar um erro em um tipo mais específico para acessar informações adicionais.

Conclusão

Lembre-se de que o tratamento de erros é uma parte fundamental do design de software em Go. Com práticas cuidadosas, você pode escrever código mais robusto e confiável.

Espero que estas dicas ajudem você a melhorar a forma como lida com erros em seus projetos Go!

Até a próxima!


Faça parte da comunidade!

Receba os melhores conteúdos sobre Go, Kubernetes, arquitetura de software, Cloud e esteja sempre atualizado com as tendências e práticas do mercado.

Livros Recomendados

Abaixo listei alguns dos melhores livros que já li sobre GO.

Deixe uma resposta