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())
    }
}()

código fonte completo

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.

Cesar Gimenes

Última modificação