Golang

Artigo 41 — Linting, formatação e análise estática: gofmt, golangci-lint Já leu

9 min de leitura

Artigo 41 — Linting, formatação e análise estática: gofmt, golangci-lint
Artigo 41 — Linting, formatação e análise estática: gofmt, golangci-lint Qualidade de código automatizada: o computador como revisor implacável Todo time de

Artigo 41 — Linting, formatação e análise estática: gofmt, golangci-lint

Curso: Dominando Go em 1 Ano Prof. Ricardo Matos Módulo 7 — Ferramentas, CLI e Qualidade


Qualidade de código automatizada: o computador como revisor implacável

Todo time de desenvolvimento acumula convenções não escritas que só existem na cabeça dos membros mais antigos. Tabs ou espaços? Onde vai o erro neste retorno? Esse lock está sendo usado corretamente? Análise estática responde a essas perguntas de forma consistente, para todos os contribuidores, em cada commit.

Go tem uma vantagem única nessa área: a linguagem foi projetada com ferramentas em mente. O compilador é rigoroso, o gofmt é universal e o ecossistema de análise estática é maduro. Integrar essas ferramentas ao fluxo de trabalho é simples e os ganhos em qualidade são imediatos.


gofmt: um único estilo para toda a comunidade

gofmt formata código Go de acordo com o estilo oficial da linguagem. Não existe configuração — não há como ajustar a indentação, a posição das chaves ou o espaçamento. Isso elimina completamente as discussões de estilo entre equipes.

# Formatar um arquivo
gofmt -w arquivo.go

# Formatar recursivamente todos os arquivos Go do projeto
gofmt -w .

# Ver o diff sem aplicar
gofmt -d arquivo.go

# Verificar se o arquivo está formatado (exit code 1 se não estiver)
gofmt -l .

gofmt também realiza simplificações automáticas com a flag -s:

# Simplifica construções redundantes
gofmt -s -w .

Exemplos de simplificações que -s aplica:

// Antes
s := []int{1, 2, 3}
for i, _ := range s { }    // → for i := range s {}
x := []int{int(1), int(2)} // → x := []int{1, 2}
a = a[0:len(a)]            // → a = a[:]

goimports: gofmt com gerenciamento de imports

goimports faz tudo que gofmt faz e ainda adiciona imports ausentes e remove imports não utilizados:

go install golang.org/x/tools/cmd/goimports@latest
goimports -w .

A maioria dos editores executa goimports automaticamente ao salvar — é um dos primeiros plugins a instalar ao configurar um ambiente Go.


go vet: análise estática da biblioteca padrão

go vet detecta erros comuns que o compilador não captura:

go vet ./...

Exemplos do que go vet detecta:

// Printf com argumento errado
fmt.Printf("%d", "texto")       // vet: Printf format %d has wrong type string

// Mutex copiado
mu := sync.Mutex{}
outraMu := mu                   // vet: assignment copies lock value

// Unreachable code após return
func f() int {
    return 1
    fmt.Println("nunca")        // vet: unreachable code
}

// String de formato inválida
fmt.Sprintf("%!")                // vet: invalid format string

// Erro ignorado em defer
defer resp.Body.Close()         // ok — mas com valor retornado ignorado

// Estrutura com tag JSON inválida
type T struct {
    Campo string `json:"campo,omitemtempty"` // vet: typo in tag
}

staticcheck: análise estática avançada

staticcheck é o analisador estático mais abrangente do ecossistema Go, cobrindo dezenas de verificações que go vet não faz:

go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck ./...
// SA1000: expressão regular inválida
regexp.MustCompile("[invalid")

// SA1006: Printf sem argumentos
fmt.Printf("mensagem sem args\n") // use Println

// SA1019: uso de função depreciada
ioutil.ReadFile("arquivo") // use os.ReadFile

// SA4006: variável atribuída mas nunca usada
x := calcular()
return nil // x nunca foi usado

// SA9003: bloco if sempre verdadeiro
if true {
    return
}

// S1000: select com apenas um case
select {
case v := <-ch:
    usar(v)
}
// simplifique para: v := <-ch

// ST1003: nome não idiomático
func getHTTPClient() {} // correto: GetHTTPClient ou httpClient

golangci-lint: o orquestrador

golangci-lint executa múltiplos linters em paralelo de forma eficiente. É a ferramenta padrão da indústria para pipelines de CI/CD.

# Instalação
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

# Executar
golangci-lint run ./...

# Com tempo limite maior para projetos grandes
golangci-lint run --timeout 5m ./...

# Mostrar apenas erros novos em relação ao branch principal
golangci-lint run --new-from-rev=main ./...

Configuração do golangci-lint

O arquivo .golangci.yml na raiz do projeto configura quais linters rodar e suas opções:

# .golangci.yml
run:
  timeout: 5m
  tests: true
  modules-download-mode: readonly

output:
  format: colored-line-number
  print-issued-lines: true
  print-linter-name: true

linters:
  disable-all: true
  enable:
    # Essenciais
    - errcheck       # verifica erros ignorados
    - gosimple       # sugere simplificações
    - govet          # análise estática padrão
    - ineffassign    # atribuições que nunca são usadas
    - staticcheck    # análise estática avançada
    - unused         # código não utilizado

    # Estilo e idioma Go
    - gofmt          # formatação
    - goimports      # imports organizados
    - misspell       # erros de ortografia em comentários
    - godot          # ponto final em comentários

    # Qualidade
    - bodyclose      # verifica fechamento de resp.Body
    - contextcheck   # uso correto de context
    - errorlint      # erros compatíveis com errors.Is/As
    - exhaustive     # switch em enums deve cobrir todos os casos
    - gocritic       # sugestões variadas de melhoria
    - noctx          # evita http.NewRequest sem context
    - prealloc       # pré-alocação de slices
    - revive         # linter de estilo configurável
    - sqlclosecheck  # verifica sql.Rows.Close()
    - unparam        # parâmetros que sempre recebem o mesmo valor

    # Segurança
    - gosec          # verifica vulnerabilidades de segurança
    - nilerr         # retornar nil em vez de err

linters-settings:
  errcheck:
    check-type-assertions: true
    check-blank: true

  govet:
    enable-all: true

  gosec:
    excludes:
      - G104  # erros ignorados em defer (já coberto por errcheck)

  revive:
    rules:
      - name: exported
        arguments:
          - checkPrivateReceivers
          - sayRepetitiveInsteadOfStutters

  gocritic:
    enabled-tags:
      - diagnostic
      - performance
      - style

  misspell:
    locale: US

  goimports:
    local-prefixes: github.com/minha-empresa

issues:
  exclude-rules:
    # Ignorar erros de lint em arquivos de teste
    - path: _test\.go
      linters:
        - errcheck
        - gosec

    # Ignorar comentários sem ponto em código gerado
    - path: \.pb\.go
      linters:
        - godot
        - revive

  max-issues-per-linter: 0
  max-same-issues: 0

Os linters mais importantes

errcheck: erros nunca devem ser silenciados

// ERRADO — errcheck detecta
os.Remove("arquivo.tmp")
json.Unmarshal(dados, &v)
rows.Close()

// CORRETO
if err := os.Remove("arquivo.tmp"); err != nil {
    log.Printf("aviso: remover temp: %v", err)
}

if err := json.Unmarshal(dados, &v); err != nil {
    return fmt.Errorf("decodificar: %w", err)
}
defer func() {
    if err := rows.Close(); err != nil {
        log.Printf("fechar rows: %v", err)
    }
}()

bodyclose: vazamentos de conexão HTTP

// ERRADO — bodyclose detecta
resp, _ := http.Get("https://exemplo.com")
dados, _ := io.ReadAll(resp.Body)

// CORRETO
resp, err := http.Get("https://exemplo.com")
if err != nil {
    return err
}
defer resp.Body.Close()
dados, err := io.ReadAll(resp.Body)

errorlint: erros compatíveis com wrapping

// ERRADO — não funciona com erros embrulhados
if err == sql.ErrNoRows { }
if err == os.ErrNotExist { }

// CORRETO — errorlint reforça uso de errors.Is
if errors.Is(err, sql.ErrNoRows) { }
if errors.Is(err, os.ErrNotExist) { }

// ERRADO — type assertion direta
if e, ok := err.(*MyError); ok { }

// CORRETO
var e *MyError
if errors.As(err, &e) { }

gosec: segurança

// G101: credencial hardcoded
senha := "minha-senha-secreta"          // gosec: hardcoded credential

// G304: abertura de arquivo com caminho do usuário
caminho := r.FormValue("arquivo")
os.Open(caminho)                         // gosec: file inclusion

// G401: algoritmo criptográfico fraco
md5.Sum(dados)                           // gosec: use sha256 ou superior

// G501: import de pacote inseguro
import "crypto/md5"                      // gosec

// Correto
import "crypto/sha256"
hash := sha256.Sum256(dados)

prealloc: performance com slices

// ERRADO — prealloc detecta
var resultado []string
for _, item := range itens {
    resultado = append(resultado, processar(item))
}

// CORRETO
resultado := make([]string, 0, len(itens))
for _, item := range itens {
    resultado = append(resultado, processar(item))
}

Integração com editores

VS Code

// .vscode/settings.json
{
    "go.lintTool": "golangci-lint",
    "go.lintFlags": ["--fast"],
    "go.formatTool": "goimports",
    "[go]": {
        "editor.formatOnSave": true,
        "editor.codeActionsOnSave": {
            "source.organizeImports": true
        }
    }
}

Makefile com targets de qualidade

# Makefile
.PHONY: lint fmt vet test ci

fmt:
    goimports -w .
    gofmt -s -w .

vet:
    go vet ./...

lint:
    golangci-lint run ./...

test:
    go test -race -count=1 ./...

test-cover:
    go test -race -count=1 -coverprofile=cobertura.out ./...
    go tool cover -html=cobertura.out -o cobertura.html

ci: fmt vet lint test
    @echo "✓ Todas as verificações passaram"

# Verifica se há arquivos não formatados (para CI)
check-fmt:
    @unformatted=$$(gofmt -l .); \
    if [ -n "$$unformatted" ]; then \
        echo "Arquivos não formatados:"; \
        echo "$$unformatted"; \
        exit 1; \
    fi

GitHub Actions: CI com qualidade automatizada

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  qualidade:
    name: Qualidade de Código
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Configurar Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'
          cache: true

      - name: Verificar formatação
        run: |
          unformatted=$(gofmt -l .)
          if [ -n "$unformatted" ]; then
            echo "Arquivos precisam de formatação:"
            echo "$unformatted"
            exit 1
          fi

      - name: go vet
        run: go vet ./...

      - name: golangci-lint
        uses: golangci/golangci-lint-action@v4
        with:
          version: latest
          args: --timeout=5m

      - name: Testes com cobertura
        run: |
          go test -race -count=1 \
            -coverprofile=cobertura.out \
            -covermode=atomic \
            ./...

      - name: Verificar cobertura mínima
        run: |
          cobertura=$(go tool cover -func=cobertura.out | \
            grep total | awk '{print $3}' | sed 's/%//')
          echo "Cobertura: ${cobertura}%"
          if (( $(echo "$cobertura < 70" | bc -l) )); then
            echo "Cobertura abaixo do mínimo de 70%"
            exit 1
          fi

      - name: Upload de cobertura
        uses: codecov/codecov-action@v4
        with:
          file: cobertura.out

Pre-commit hooks com lefthook

go install github.com/evilmartians/lefthook@latest
# lefthook.yml
pre-commit:
  parallel: true
  commands:
    fmt:
      glob: "*.go"
      run: goimports -w {staged_files}

    vet:
      glob: "*.go"
      run: go vet ./...

    lint:
      glob: "*.go"
      run: golangci-lint run --new-from-rev=HEAD~1 {staged_files}

pre-push:
  commands:
    tests:
      run: go test -race ./...
lefthook install  # instala os hooks no repositório git

Baseline: adotando lint em projeto existente

Ao adotar golangci-lint em um projeto com muitos problemas existentes, a abordagem incremental é mais prática:

# Gera um baseline com todos os problemas atuais
golangci-lint run --out-format=json ./... > .golangci-baseline.json

# Nas execuções seguintes, reporta apenas novos problemas
golangci-lint run --baseline=.golangci-baseline.json ./...

Ou usando a flag --new-from-rev para reportar apenas problemas introduzidos após um commit específico:

# Reporta apenas problemas em código alterado desde o branch main
golangci-lint run --new-from-rev=origin/main ./...

Resumo do que foi coberto

Este artigo apresentou análise estática e qualidade de código em Go de forma completa: gofmt e goimports para formatação uniforme, go vet para detecção de erros comuns, staticcheck para análise avançada, configuração detalhada do golangci-lint com os linters mais importantes — errcheck, bodyclose, errorlint, gosec e prealloc — integração com VS Code, Makefile com targets de qualidade, pipeline de CI com GitHub Actions incluindo verificação de cobertura e pre-commit hooks com lefthook. O próximo artigo aborda documentação com godoc.


Referências e leituras complementares

  • golangci-lint — documentação oficial — Lista completa de linters e configuração. https://golangci-lint.run

  • staticcheck — Documentação de todas as verificações disponíveis. https://staticcheck.dev

  • gosec — Verificações de segurança para código Go. https://github.com/securego/gosec

  • Documentação gofmt — Referência e opções do formatador oficial. https://pkg.go.dev/cmd/gofmt

  • lefthook — Gerenciador de git hooks. https://github.com/evilmartians/lefthook

  • Go Code Review Comments — Guia oficial de boas práticas de código Go. https://github.com/golang/go/wiki/CodeReviewComments


Próximo artigo: Artigo 42 — Documentação com godoc e boas práticas de comentário**


you asked

Sim


claude response

Comentários

Mais em Golang

Logging estruturado com slog e Zerolog
Logging estruturado com slog e Zerolog

Logs são a primeira e muitas vezes única fonte de informação quando algo dá e...

Leitura e escrita de arquivos com os pacotes os e bufio
Leitura e escrita de arquivos com os pacotes os e bufio

Antes de bancos de dados, filas de mensagens e APIs REST, existe o arquivo. L...

Artigo 43 — Profiling e otimização de performance em Go
Artigo 43 — Profiling e otimização de performance em Go

Artigo 43 — Profiling e otimização de performance em Go Otimizar sem medir é...