Boas práticas em GO: como organizar códigos e projetos em Go

Boas práticas em GO: como organizar códigos e projetos em Go
Juliana Amoasei
Juliana Amoasei

Compartilhe

Como organizar o código Go de forma clara, eficiente e fácil de manter? Neste artigo, vamos conhecer as armadilhas comuns e como evitá-las, além das boas práticas para estruturar projetos e usar recursos como interfaces, funções init e pacotes utilitários.

Muitas pessoas programam em Go de forma ineficiente porque aplicam padrões de outras linguagens sem considerar as particularidades do Go.

É comum que pessoas desenvolvedoras de linguagens orientadas a objetos exagerem no uso de interfaces e abstrações desnecessárias, deixando o código mais complexo do que deveria.

Outra armadilha comum é não seguir a organização idiomática de pacotes, misturando responsabilidades ou criando uma estrutura excessivamente fragmentada, dificultando a manutenção e a escalabilidade do projeto.

Continue lendo o artigo para descobrir como evitar os erros mais comuns e aprender a adotar práticas que vão tornar seu desenvolvimento mais ágil e melhorar seu código Go.

Sombreamento de variáveis

Declarar uma variável com o mesmo nome em um bloco interno esconde a variável do bloco externo. Isso pode causar confusão e bugs difíceis de encontrar.

nome := "João" // Nome externo
{
    nome := "Maria" // Nome interno, sombreia o externo
    fmt.Println(nome) // Imprime "Maria"
}
fmt.Println(nome) // Imprime "João" - o nome externo não foi alterado

Solução: Use nomes diferentes para variáveis em blocos internos.

package main

import "fmt"

func main() {
    nomeExterno := "João" // Nome externo
    {
        nomeInterno := "Maria" // Nome interno, não sombreia o externo
        fmt.Println(nomeInterno) // Imprime "Maria"
    }
    fmt.Println(nomeExterno) // Imprime "João" - o nome externo não foi alterado
}
Banner da Imersão Dev da Alura com fundo escuro e destaque para a frase 'A Imersão Dev está com inscrições abertas'. Texto informando que são cinco aulas 100% gratuitas para aprender programação do zero. Imagem de um laptop exibindo um certificado digital e um botão chamativo com a frase 'Garanta a sua vaga'.

Código aninhado em excesso

Muitos níveis de if/else dificultam a leitura e compreensão do código.

func process(n int) {
    if n > 0 {
        if n%2 == 0 {
            fmt.Println("Número positivo e par")
        } else {
            fmt.Println("Número positivo e ímpar")
        }
    } else {
        fmt.Println("Número não é positivo")
    }
}

Solução: Simplifique as condições, retorne antecipadamente, quando possível, e alinhe o caminho principal (o código que executa na maioria das vezes) à esquerda, como no exemplo abaixo:

func process(n int) {
    if n <= 0 {
        fmt.Println("Número não é positivo")
        return
    }
    if n%2 == 0 {
        fmt.Println("Número positivo e par")
        return
    }
    fmt.Println("Número positivo e ímpar")
}

Mau uso de funções init

Funções init são executadas automaticamente ao iniciar um pacote. Evite usá-las para tarefas complexas ou que possam falhar, pois o tratamento de erros é limitado.

var config Config

func init() {
    config = loadConfig() // Pode falhar e não tem tratamento de erro adequado
}

Solução: Prefira funções normais para inicializações.

var config Config

func initialize() error {
    var err error
    config, err = loadConfig()
    return err
}

Excesso de getters e setters

Em Go, não é idiomático usar getters e setters para todos os campos de uma struct, como no exemplo abaixo:

type User struct {
    name string
}

func (u *User) GetName() string {
    return u.name
}

func (u *User) SetName(name string) {
    u.name = name
}

Solução: Acesse os campos diretamente, a menos que haja uma lógica específica para encapsular.

type User struct {
    Name string // Campos exportados (maiúscula inicial) são acessíveis externamente
}

Poluição de interfaces

Criar interfaces desnecessárias torna o código mais complexo. Interfaces devem ser descobertas conforme a necessidade, e não criadas antecipadamente.

type Database interface {
    Save(data string)
}

type SQLDatabase struct{}

func (s SQLDatabase) Save(data string) {
    fmt.Println("Salvando no banco")
}

Solução: Prefira interfaces pequenas e com propósito claro.

type SQLDatabase struct{}

func (s SQLDatabase) Save(data string) {
    fmt.Println("Salvando no banco")
}

Interfaces no lado do produtor

Quando a interface é definida no pacote produtor, qualquer mudança na interface pode exigir alterações em todos os pacotes que a utilizam. Isso cria um acoplamento desnecessário entre o produtor e os consumidores.

package storage

type Storage interface {
    Save(data string)
}

Solução: Defina interfaces no pacote que as utiliza (consumidor), não no pacote que implementa os tipos concretos (produtor). Isso dá mais flexibilidade ao consumidor.

package service

type Storage interface {
    Save(data string)
}

any não diz nada

Em Go, any é um alias para interface{} e representa qualquer tipo.

Ele foi introduzido no Go 1.18 para tornar o código mais legível e intuitivo, substituindo o uso explícito de interface{} para indicar valores de qualquer tipo.

Entretanto, any pode receber qualquer tipo, mas isso torna o código menos expressivo.

func PrintValue(value any) {
    fmt.Println(value)
}

Solução: Use tipos concretos sempre que possível.

func PrintValue(value string) {
    fmt.Println(value)
}

Genéricos

Genéricos permitem escrever código que funciona com diferentes tipos. Use-os com cautela, apenas quando necessário para evitar repetição de código com tipos diferentes.

func Print[T any](value T) {
    fmt.Println(value)
}

Solução: Não use genéricos quando tornar o código mais complexo.

package main

import "fmt"

func PrintInt(value int) {
    fmt.Println(value)
}

func PrintString(value string) {
    fmt.Println(value)
}

func main() {
    PrintInt(42)
    PrintString("Olá, mundo!")
}

Desorganização do projeto

Organize o projeto em pacotes com nomes claros e concisos.

package main

type User struct{}
func saveUser() {}
func main() { ………

Evite pacotes muito pequenos ou muito grandes. Separe código público de código privado.

/project
  /cmd
  /internal
  /pkg
  /service
  /repository

Ignorar colisões de nomes de pacotes

Evite nomes de variáveis que colidam com nomes de pacotes.

package utils

func ConvertToUpper(s string) string {
    return strings.ToUpper(s)
}

Solução: Use nomes diferentes ou aliases para pacotes.

package stringutil

func ToUpper(s string) string {
    return strings.ToUpper(s)
}

Não usar linters

Linters são ferramentas que analisam o código em busca de erros e problemas de estilo.

go run main.go

Solução: Use linters para garantir a qualidade do código.

golangci-lint run

Conclusão

Escreva código Go simples, claro e bem organizado. Evite abstrações desnecessárias e use as ferramentas disponíveis para garantir a qualidade do código.

Lembre-se que a consistência é fundamental para facilitar a manutenção do projeto.

Quer aprofundar ainda nessa linguagem? Aprenda a programar em Go com boas práticas na Alura! Conheça as formações Linguagem Go e Aprenda a programar em Go.

Juliana Amoasei
Juliana Amoasei

Desenvolvedora JavaScript com background multidisciplinar, sempre aprendendo para ensinar e vice-versa. Acredito no potencial do conhecimento como agente de mudança pessoal e social. Atuo como instrutora na Escola de Programação da Alura e, fora da tela preta, me dedico ao Kung Fu e a nerdices em geral.

Veja outros artigos sobre Programação