Aproveite o mês das
carreiras na Alura

Até 44% OFF

Falta pouco!

00

DIAS

00

HORAS

00

MIN

00

SEG

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

Guilherme Lima
Guilherme Lima

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 de IA da Alura com Google Gemini. Participe de aulas gratuitas online com certificado. Domine as inovações mais recentes da IA.

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.

Guilherme Lima
Guilherme Lima

Guilherme é desenvolvedor de software formado em Sistemas de Informação e possui experiência em programação usando diferentes tecnologias como Python, Javascript e Go. Criador de mais de 30 cursos de diferentes áreas da plataforma com foco no treinamento de profissionais de TI, como Data Science, Python para web com Django e Django Rest, jogos com Javascript, Infraestrutura como código com Terraform e Ansible, Orientação a Objetos com Go. Além disso, é um dos instrutores da Imersão Dev da Alura.

Veja outros artigos sobre Programação