Shadow de variáveis em Golang

Golang é uma linguagem de programação fácil de usar e que ajuda a evitar muitos dos erros comuns.

Mas um erro que é fácil de cometer em Go e que ainda não gera warnings do compilador é shadow de variáveis.

Shadow é quando você declara uma variável em um bloco de código e em um bloco aninhado declara novamente a variável.

Daí você faz alterações no valor da variável acreditando que esta atualizando a variável do bloco de hierarquia superior mas na verdade esta alterando apenas a variável do bloco atual.

Exemplo

No exemplo a seguir a variável x é declarada no bloco principal mas logo em seguida dentro do bloco do if usamos := para declarar a variável err e isso faz com que x fosse recriada nesse bloco.

Quando o bloco de código do if terminar essa variável deixara de existir e a instância anterior de x vai voltar a valer.

package main

import "fmt"

var a = 1

func ret10() (int, error) {
    return 10, nil
}

func main() {
    x := 1
    if a == 1 {
        x, err := ret10()
        if err != nil {
            fmt.Println(err)
            return
        }
        fmt.Println("x =", x)
    }
    fmt.Println("x =", x)
}

Como resultado o primeiro fmt.Println vai retornar mostrar que x = 10 e o segundo vai mostrar x = 1.

Erros assim acabam sendo bem difíceis de depurar porque tudo parece normal a primeira vista.

Detecção experimental

Ainda é experimental mas já existe uma forma de detectar shadow de variáveis usando as ferramentas go vet.

Instalando utilitário

A primeira coisa a fazer é instalar o utilitário do go vet que vai nos ajudar a procurar por variáveis “sombreadas” no nosso código.

Para isso use o seguinte comando:

go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow

Note que é importante que o diretório go/bin esteja na variável PATH.

Procurando por shadow

Em seguida basta executar seguinte comando no diretório do seu projeto.

go vet -vettool=$(which shadow)

No caso do nosso exemplo o retorno sera o seguinte:

./main.go:14:3: declaration of "x" shadows declaration at line 12

Agora sabemos exatamente onde estamos fazendo shadowing e fica bem mais fácil de resolver o problema.

Além disso go vet também vai alterar o valor de errorlevel (ou $? no mundo UNIX), então podemos automatizar para que ferramentas de CI ou mesmo um Makefile não permita que compilação continue.

Testes

Nem precisa dizer que testes ajuda muito a prevenir que você seja pego de surpresa por shadow de variáveis.

Seria bem simples escrever testes para garantir que sua função esta se comportando como deveria.

Mesmo sem go vet talvez leve algum tempo para descobrir o motivo dos testes estarem falhando e faça coçar a cabeça algumas vezes mas pelo menos você não vai descobrir o problema em produção adivinha quem não tinha cobertura de testes nessa situação

Outra forma de shadow

Como em Go variáveis e funções tem o mesmo peso, é possível fazer shadow de uma função com uma variável.

Esse erro não é um problema muito grande porque o compilador vai emitir um erro se tentarmos usar uma função que foi sobreposta por uma variável, mas ainda assim é um inconveniente.

Veja o seguinte exemplo:

package main

import "fmt"

func main() {
    len := 10 // shadow função builtin len()
    fmt.Printf("len = %v\n", len)
}

Nesse exemplo a função len esta sobrepondo a função len do runtime, se tentarmos usar essa função em qualquer parte do nosso código o compilador vai emitir um erro.

Cesar Gimenes