Dicas de Golang defer
Usar defer para adiar um comando até a função terminar, independentemente de como ela termine, é uma excelente ideia. Imagine uma função com vários pontos de retorno, seja para erros ou para terminar corretamente. Gerenciar o fechamento de arquivos, conexões, mutexes e outros recursos em cada retorno torna-se entediante e inseguro. Você pode esquecer de fechar um recurso em algum retorno, e isso pode causar problemas sérios.
Contudo, o uso de defer
tem um custo. O sistema empilha o comando adiado, e adiá-lo com defer
é alguns nanossegundos mais lento do que uma chamada direta. Isso geralmente não é um problema no dia a dia, mas se você precisa maximizar a performance do seu código, pode ser algo a considerar.
O uso mais comum de defer
é liberar recursos, como fechar um arquivo. Porém, qualquer comando pode ser adiado. Por exemplo, se eu tenho uma função complexa que realiza várias tarefas e não pode rodar em paralelo no sistema, uso um mutex para sincronizar o código e evitar colisões. Como a função possui vários pontos de retorno, defer
ajuda a garantir que o bloqueio seja liberado.
m := &sync.Mutex{}
...
m.Lock()
defer m.Unlock()
Fique atento
Outra coisa importante é evitar sobrepor retornos nomeados com defer
. Por exemplo, suponha que você abriu um arquivo e usou defer
para garantir que ele seja fechado.
f, err = os.Open("filename.ext")
if err != nil {
log.Fatal(err)
}
defer f.Close()
O código acima é simples e eficiente. Porém, se você deseja tratar o erro ao fechar o arquivo, pode fazer o seguinte.
defer func() {
err = f.Close()
if err != nil {
println(err.Error())
}
}()
A princípio, tudo parece bem. Você tratou corretamente o possível erro da função Close
. O problema é que a variável err é um retorno nomeado da sua função. Como a função anônima será chamada ao final da rotina principal, err será sobrescrito pelo retorno de Close()
. Assim, você nunca obterá o erro real da função e ficará confuso ao não receber o resultado esperado ou qualquer erro.
Existem várias formas de resolver esse problema. Por exemplo, você pode usar outra variável no lugar de err, ou simplesmente ignorar esse erro, já que é improvável que ele seja relevante. Outra opção é substituir o log por um panic. Vale lembrar que esse não é um problema exclusivo do defer
. Teoricamente, você pode enfrentar o mesmo problema com goroutines, o que pode levar a situações de data race.
Outro erro comum com defer
é chamá-lo dentro de um loop. Defer
empilha a função passada e a executa na ordem inversa, apenas ao retornar da função. Se você usar defer
dentro de um loop, a função será empilhada várias vezes, podendo causar um estouro de pilha. Por exemplo:
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
Apesar desses detalhes a serem considerados, é uma boa prática usar defer
para garantir que os recursos alocados sejam liberados.