Go: Estilo, Decisões e Melhores Práticas

Leo CavalcanteLeo Cavalcante
5 min read

O guia de estilo, decisões e melhores práticas da Google para a linguagem Go é um conjunto de recomendações que visa promover a clareza, simplicidade e eficiência no desenvolvimento de código.

Essas diretrizes ajudam os desenvolvedores a escreverem código legível e sustentável, essencial para projetos de longo prazo. Neste artigo, exploramos os principais pontos abordados nesses documentos, com ênfase na importância da consistência, da manutenção e da colaboração dentro das equipes de desenvolvimento.

1. Estilo de Código

Manter um estilo de código consistente é essencial para a colaboração em equipe e a manutenção de projetos. Isso também facilita revisões de código e onboarding de novos membros na equipe.

1.1 Formatação

Use gofmt para garantir formatação padronizada. O uso dessa ferramenta elimina ambiguidades sobre espaçamento, indentacão e organização de código. Além disso, integra-se facilmente com IDEs e pipelines de CI/CD.

Boas práticas adicionais:

  • Evite linhas com mais de 80 caracteres para melhorar a legibilidade.

  • Use espaços em vez de tabulações para garantir compatibilidade em diferentes editores.

Exemplo:

// Formatação correta
func Add(a int, b int) int {
    return a + b
}

// Formatação incorreta
func Add(a int, b int) int {
return a + b // Errado: indentacão incorreta
}

1.2 Convenções de Nomes

Nomes de variáveis, funções e pacotes devem ser claros e descritivos, refletindo suas responsabilidades. Isso melhora a manutenção e reduz a necessidade de comentários extensivos.

Regras principais:

  • camelCase: Use para variáveis, funções e argumentos internos.

  • PascalCase: Use para funções, estruturas e constantes exportadas.

  • snake_case: Evite este padrão em código Go.

  • Pacotes devem usar nomes curtos e em minúsculas, sem underscores.

Exemplo:

// Correto
func CalculateSum(a int, b int) int {
    total := a + b
    return total
}

// Errado
func calculatesum(a int, b int) int {
    total := a + b
    return total
}

Nomes de pacotes:

// Correto
package mathutils

// Errado
package math_utils

1.3 Comentários

Adicione comentários explicativos que descrevam o "por quê" e não o "como". Comentários excessivos ou redundantes devem ser evitados. Para funções exportadas, siga o formato padrão do Go, onde o comentário começa com o nome da função.

Melhores práticas:

  • Use docstrings para pacotes e funções importantes.

  • Comentários devem ser atualizados sempre que o comportamento do código mudar.

Exemplo:

// CalculateSum soma dois inteiros e retorna o resultado.
func CalculateSum(a int, b int) int {
    return a + b
}

// Comentário redundante e desnecessário
// A função retorna a soma de dois inteiros
func Sum(a, b int) int {
    return a + b
}

2. Decisões de Projeto

Decisões claras sobre organização de projetos e uso de recursos da linguagem ajudam a evitar confusões, bugs e retrabalho. Um design bem pensado também facilita a expansão do projeto no futuro.

2.1 Estrutura de Projetos

A organização do código deve ser modular, intuitiva e refletir os objetivos principais do sistema. Adote uma estrutura de diretórios que favoreça a separação de preocupações.

Diretrizes:

  • Evite pacotes genéricos como utils sempre que possível. Prefira nomes específicos.

  • Mantenha o main.go limpo e delegue a lógica para outros pacotes.

  • Utilize padrões como internal/ para código que não deve ser acessado fora do repositório.

Estrutura sugerida:

project/
    cmd/
        app/
            main.go
    mathutils/
        math.go
    internal/
        db/
            connection.go

2.2 Interfaces

Interfaces são mais eficazes quando são pequenas e representam uma única responsabilidade. Isso permite maior flexibilidade na implementação e facilita testes.

Princípios:

  • Evite adicionar métodos à interface antes de necessários.

  • Use composição para criar interfaces mais complexas.

Exemplo:

// Correto
type Reader interface {
    Read(p []byte) (n int, err error)
}

// Composição de interfaces
type ReadWriter interface {
    Reader
    Write(p []byte) (n int, err error)
}

// Errado: Interface genérica e inflexível
type Service interface {
    Start()
    Stop()
    Restart()
}

2.3 Controle de Dependências

O gerenciamento de dependências é crucial para manter a segurança e a estabilidade do projeto. Use go mod para controlar dependências de forma eficiente.

Boas práticas:

  • Atualize dependências regularmente e teste cuidadosamente antes de fazer o merge.

  • Use ferramentas como dependabot para monitorar atualizações.

Exemplo:

module example.com/project

go 1.20

require (
    github.com/pkg/errors v0.9.1
)

3. Melhores Práticas

3.1 Tratamento de Erros

A gestão de erros em Go é projetada para ser simples e explícita. Sempre trate erros imediatamente ou propague-os com contexto adicional.

Dicas:

  • Use errors.Is e errors.As para verificar e desembrulhar erros.

  • Padronize mensagens de erro para facilitar a depuração.

Exemplo:

if err := doSomething(); err != nil {
    if errors.Is(err, ErrSpecific) {
        return fmt.Errorf("erro especifico: %w", err)
    }
    return fmt.Errorf("erro genérico: %w", err)
}

3.2 Concorrência

O suporte embutido a concorrência é uma das maiores forças do Go. No entanto, erros comuns como vazamentos de Goroutines ou acessos concorrentes não sincronizados devem ser evitados.

Ferramentas úteis:

  • sync.WaitGroup para esperar várias Goroutines.

  • sync.Mutex para proteger seções críticas.

  • context.Context para cancelar Goroutines.

Exemplo com Context:

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

ch := make(chan int)

go func() {
    defer close(ch)
    for i := 0; i < 5; i++ {
        select {
        case <-ctx.Done():
            return
        case ch <- i:
        }
    }
}()

for val := range ch {
    fmt.Println(val)
}

3.3 Logging

Logs estruturados ajudam na monitoração e diagnóstico de problemas em produção. Prefira bibliotecas como log/slog para logs mais ricos e configuráveis.

Exemplo:

package main

import (
    "log/slog"
)

func main() {
    // Criando um logger padrão
    logger := slog.Default()

    // Registrando uma mensagem de informação com campos
    logger.Info("iniciando operação",
        slog.Int("valor", 42),
        slog.String("status", "inicializado"))
}

3.4 Testes

Adote TDD (Test-Driven Development) sempre que possível. Utilize subtestes para dividir testes complexos em partes menores e módulos de mocks para simular dependências externas.

Exemplo com mocks:

type MockService struct {}

func (m *MockService) Process(data string) error {
    if data == "error" {
        return errors.New("mock error")
    }
    return nil
}

func TestService(t *testing.T) {
    service := &MockService{}
    err := service.Process("error")
    if err == nil {
        t.Fatalf("esperava erro, mas obteve nil")
    }
}

Em resumo, os guias de estilo, decisões e melhores práticas recomendados pela Google para Go são fundamentais para garantir que o código seja claro, conciso e fácil de manter. A adesão a essas orientações não só melhora a qualidade do código, mas também facilita a colaboração em equipes de desenvolvimento. Seguir essas práticas promove a consistência e a legibilidade, elementos essenciais para projetos de longo prazo bem-sucedidos. Para mais informações, consulte os recursos completos no site oficial.

0
Subscribe to my newsletter

Read articles from Leo Cavalcante directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Leo Cavalcante
Leo Cavalcante

Runtimes Engineer and Developer Experience at PicPay.