Dicas de Go defer

Defer ajuda muito mas também oferece algum risco veja algumas dicas de uso

Defer

Usar defer e poder adiar um comando para ser executado apenas quando a função terminar não importando a forma como ela terminar é uma grande ideia. Imagine uma função com vários pontos de retorno, seja para retornar erros ou porque ela terminou corretamente, ficar gerenciando fechar arquivos, conexões, mutex e o que mais você estiver usando em cada retorno é entediante e inseguro, você pode esquecer um dos retornos, e pode apostar que esse único retorno que você esqueceu é que vai tirar horas do seu sono.

Mas defer não vem de graça como o sistema vai empilhar o comando que você mandou, adiar uma chamada usando defer é alguns microsegundos mas lento que uma chamada direta, nada para se preocupar no dia a dia mas se você precisa extrair cada grama de performace do seu código pode ser um ponto para observar… (ou talvez escrever em C haha)

O uso mais comum de defer é para liberar recursos, como fechar um arquivo por exemplo mas qualquer comando pode ser “adiado” por exemplo eu tenho uma função complexa que faz muitas coisas e não pode rodar em paralelo no sistema, então eu usei um mutex para sincronizar o código e garantir que não haveria colisão, mas como a função tem muitos pontos de retorno defer me ajudou a garantir que o lock seja liberado.

m := &sync.Mutex{}
.
.
.
m.Lock()
defer m.Unlock()

Fique atento

Outra coisa para ter em mente é que você precisa tomar cuidado para não sobrepor retornos nomeados com defer, por exemplo vamos dizer que você abriu um arquivo e vai usar defer para garantir que o arquivo seja fechado.

f, err = os.Open("filename.ext")
if err != nil {
    log.Fatal(err)
}
defer f.Close()

Só que o gometalinter marcou com um warning a linha do defer porque a função Close() retorna um erro e você não esta tratando esse erro. Você considera isso inaceitável porque você segue segue minha cartilha que diz que warnings são erros! Então você cria uma função anônima para ser chamada pelo defer que vai fechar o arquivo e tratar esse erro.

defer func() {
	err = f.Close()
	if err != nil {
		println(err.Error())
	} 
}()

A primeira vista esta tudo bem, você tratou o erro, o warning sumiu e você voltou a ter paz de espirito, o único problema é que a variável err é um retorno nomeado da sua função e como a função anônima vai ser chamada quando a rotina principal chegar ao fim, err vai ser sobrescrito pelo retorno de Close() e você nunca vai conseguir pegar o erro real da função, e vai passar horas se perguntando porque não recebe o resultado esperado e ao mesmo tempo nenhum erro.

Existem muitas formas de resolver esse problema, por exemplo podemos usar outra variável no lugar de err, ou podemos simplesmente ignorar esse erro já que é improvável que ele seja relevante, ou ser radical e trocar o log por um panic. E também é bom lembrar que esse não é um problema exclusivo do defer, teoricamente você pode ter o mesmo problema com goroutines e até acabar com usa situação de data race.

Apesar desses detalhes que temos que observar no geral é uma boa pratica usar defer e garantir que os recursos alocados sejam liberados, por exemplo, recentemente no projeto pREST um belo memory leak foi resolvido simplesmente usando defer para garantir que a conexão com o banco seria fechada.

comments powered by Disqus