Escrevendo Go idiomático
LidoNesta aula, você aprenderá a escrever código Go idiomático, seguindo as convenções e princípios estabelecidos pela comunidade. Abordaremos os conceitos do Effective Go, boas práticas de simplicidade, convenções de nomenclatura e como conduzir um code review eficaz. Ao final, você terá as ferramentas necessárias para produzir código limpo, consistente e de alta qualidade.
Escrever código Go idiomático é mais do que apenas seguir uma sintaxe; é adotar um conjunto de convenções e princípios que tornam o código claro, consistente e fácil de manter. Nesta aula, vamos explorar os fundamentos do Effective Go, as convenções de nomenclatura e formatação, a importância da simplicidade e como conduzir code reviews eficientes. Ao dominar esses aspectos, você será capaz de produzir código que não apenas funciona, mas que também é agradável de ler e colaborar.
Go foi projetado com a simplicidade em mente, e isso se reflete em suas convenções. A comunidade Go valoriza código legível, sem excessos, e que siga padrões bem estabelecidos. Vamos mergulhar em cada um desses tópicos para que você possa internalizar essas práticas.
Effective Go (princípios)
O documento Effective Go é a referência oficial da linguagem para escrever código idiomático. Ele traz diretrizes que vão desde formatação até design de APIs. O princípio central é que o código deve ser claro e expressivo, aproveitando os recursos da linguagem de forma que o leitor entenda rapidamente a intenção.
Um dos exemplos mais conhecidos do Effective Go é o tratamento de erros. Em Go, erros são valores e devem ser tratados explicitamente. Isso incentiva o programador a pensar sobre os caminhos de erro desde o início. Outro princípio é a composição: em vez de herança, Go usa interfaces e composição de structs para reutilizar código.
Vamos ver um exemplo de tratamento de erro idiomático:
package main
import (
"fmt"
"os"
)
func readFile(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("leitura de arquivo falhou: %w", err)
}
return data, nil
}
func main() {
data, err := readFile("config.txt")
if err != nil {
fmt.Fprintf(os.Stderr, "erro: %v\n", err)
os.Exit(1)
}
fmt.Println(string(data))
}Note como o erro é propagado com contexto adicional usando %w, permitindo que o chamador use errors.Is ou errors.As. Esse padrão é amplamente recomendado no Effective Go.
Convenções
As convenções em Go são rigorosas e amplamente adotadas. A mais importante é a formatação automática com gofmt. Todo código Go deve ser formatado com gofmt, pois isso elimina disputas sobre estilo e garante consistência em todos os projetos. Além disso, há convenções de nomenclatura: use CamelCase para nomes exportados (começando com maiúscula) e camelCase para não exportados. Nomes curtos e concisos são preferidos, especialmente para variáveis locais.
Outra convenção importante é a organização de imports: agrupe imports em blocos: primeiro os da biblioteca padrão, depois os de terceiros. O ferramenta goimports gerencia isso automaticamente. Nomes de funções devem ser verbos, e nomes de tipos devem ser substantivos. Por exemplo, func NewServer() ou type Server struct.
Exemplo de convenções de nomenclatura:
package main
// Exportado: começa com maiúscula
type Servidor struct {
Porta int
}
// Não exportado: começa com minúscula
type config struct {
timeout time.Duration
}
func (s *Servidor) Iniciar() error {
// método exportado
return nil
}
func (c *config) setDefault() {
// método não exportado
}Embora este código seja válido, a convenção em Go é usar nomes em inglês. Portanto, prefira Server e Start.
Simplicidade
Go promove a simplicidade: menos é mais. Isso significa evitar abstrações desnecessárias, preferir funções pequenas e claras, e usar as estruturas de dados da linguagem em vez de reinventar a roda. Por exemplo, em vez de criar hierarquias complexas de tipos, use interfaces pequenas e compostas.
Outra prática é preferir a concisão: use := para inferência de tipo, mas não abuse. Se a variável tem um tipo claro, a inferência é boa. Evite código rebuscado; o objetivo é que qualquer pessoa que leia entenda rapidamente.
Exemplo de simplicidade: em vez de um construtor com muitos parâmetros, use o padrão de opções funcionais, que é idiomático em Go.
type Config struct {
Host string
Port int
}
type Option func(*Config)
func WithHost(host string) Option {
return func(c *Config) {
c.Host = host
}
}
func WithPort(port int) Option {
return func(c *Config) {
c.Port = port
}
}
func NewServer(opts ...Option) *Server {
cfg := &Config{Host: "localhost", Port: 8080}
for _, opt := range opts {
opt(cfg)
}
return &Server{cfg: cfg}
}Esse padrão mantém a API limpa e flexível, sem sobrecarregar o construtor.
Code review
Code review é uma prática essencial para manter a qualidade do código. Em Go, isso envolve verificar se o código segue as convenções, se está claro e se não há erros comuns. Ao revisar, preste atenção a pontos como: o código é idiomático? Usa gofmt? Os erros são tratados adequadamente? Há testes suficientes?
Ferramentas como go vet e staticcheck ajudam a identificar problemas. Além disso, a revisão deve ser construtiva, sugerindo melhorias e explicando o porquê. Lembre-se de que o objetivo é o aprendizado e a melhoria contínua.
Um checklist básico para code review em Go:
- O código está formatado com
gofmt? - Os nomes são descritivos e seguem as convenções?
- Os erros são tratados e propagados corretamente?
- As funções são pequenas e fazem uma única coisa?
- Há testes para os casos principais e de erro?
- O código é simples e legível?
Exemplo de uma função que pode ser melhorada:
func process(data []byte) ([]byte, error) {
if len(data) == 0 {
return nil, errors.New("dados vazios")
}
// processa...
return result, nil
}Durante a revisão, você pode sugerir o uso de fmt.Errorf com contexto, ou a criação de um tipo de erro customizado.
Boas práticas e observações finais
Além dos tópicos principais, algumas práticas adicionais ajudam a escrever código Go idiomático: use goroutines com cautela, sempre evitando vazamentos; prefira canais para comunicação entre goroutines; e documente funções exportadas com comentários que comecem com o nome da função. A documentação é parte do idioma, e o go doc depende dela.
Lembre-se de que escrever código idiomático é uma jornada contínua. Leia código de projetos de sucesso, participe de comunidades e pratique. Com o tempo, essas convenções se tornarão naturais.
Referências
- Effective Go - Documentação Oficial
- How to Write Go Code
- The Go Blog
- Pkg.go.dev - Documentação de Pacotes
- Code Review Comments (Wiki)
- Google Go Style Guide
Exercícios
- Refatore o código abaixo para seguir as convenções de nomenclatura e formatação de Go. Explique as mudanças.
package main import "fmt" type pessoa struct { Nome string idade int } func (p pessoa) getNome() string { return p.Nome } func (p *pessoa) setNome(n string) { p.Nome = n } func main() { p := pessoa{Nome: "João"} fmt.Println(p.getNome()) }✓ Resposta: O código deve usar nomes em inglês, métodos exportados começando com maiúscula e campos exportados também. Formate comgofmt. Exemplo:package main import "fmt" type Person struct { Name string age int } func (p Person) GetName() string { return p.Name } func (p *Person) SetName(name string) { p.Name = name } func main() { p := Person{Name: "John"} fmt.Println(p.GetName()) } - Escreva uma função
Divideque receba dois inteiros e retorne o quociente e um erro se o divisor for zero. Use o padrão idiomático de tratamento de erro.
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("divisão por zero")
}
return a / b, nil
}func Process(data []string) []string {
result := make([]string, 0)
for _, item := range data {
if len(item) > 0 {
trimmed := strings.TrimSpace(item)
if trimmed != "" {
result = append(result, strings.ToUpper(trimmed))
}
}
}
return result
}len(item) > 0 é redundante, pois strings.TrimSpace já lida com isso; 2) A dupla verificação de vazio pode ser simplificada; 3) O código pode ser mais direto. Melhoria:func Process(data []string) []string {
result := make([]string, 0)
for _, item := range data {
trimmed := strings.TrimSpace(item)
if trimmed != "" {
result = append(result, strings.ToUpper(trimmed))
}
}
return result
}func GetUser(id int) (*User, error) {
if id <= 0 {
return nil, errors.New("id inválido")
}
// ... busca no banco
return user, nil
}fmt.Errorf para adicionar contexto ao erro; 2) Considere usar um tipo de erro customizado para erros de validação; 3) Garanta que a busca no banco trate erros de forma adequada, retornando um erro com contexto.Concat que concatena uma lista de strings com um separador, mas usando o padrão idiomático de variáveis e tratamento de erro. A função deve retornar um erro se a lista for vazia.func Concat(parts []string, sep string) (string, error) {
if len(parts) == 0 {
return "", errors.New("lista vazia")
}
return strings.Join(parts, sep), nil
}