trilha

Tecnologias/01 - Linguagens/01 - Go5 min

Go

Conceito

Go nasceu no Google em 2007 para resolver um problema que não era de linguagem, era de organização: uma base de código C++ enorme, com compilação medida em minutos, grafo de dependências ingovernável e concorrência resolvida por biblioteca, mão a mão com threads.

A pergunta de design não foi "como escrever menos código". Foi:

Como manter milhares de engenheiros produtivos numa base de código que ninguém conhece por inteiro?

Quase toda escolha do Go sai daí:

Decisão O que ela protege
Compilação em segundos ciclo de feedback; ninguém "espera o build"
Imports não usados são erro grafo de dependências não apodrece
gofmt canônico, sem opções zero discussão de estilo em code review
Sem herança, só composição não existe hierarquia de 6 níveis para entender
Erros como valores, sem try/catch o caminho de falha está visível na leitura linear
Concorrência na linguagem go f() em vez de biblioteca de threads por projeto
Binário estático único deploy é copiar um arquivo
Biblioteca padrão grande menos dependências de terceiros para auditar
Promessa de compatibilidade Go 1 código de 2012 compila hoje

O que Go recusou de propósito

Isto é o mais informativo da linguagem:

  • Herança de classes → só struct + embedding + interfaces. Ver 4. Composição vs Herança.
  • Exceções → error é um valor de retorno. panic existe, mas é para bug, não para erro esperado.
  • Genéricos → só chegaram no Go 1.18 (2022), 13 anos depois, e ainda são deliberadamente limitados. O Go 1.27 finalmente permitiu métodos genéricos.
  • Sobrecarga de operadores, construtores, anotações, macros → não existem.
  • Enum / tipos-soma / Option → não existem; o zero value faz esse papel, mal.

O modelo mental

Go é uma linguagem compilada, com coletor de lixo, tipagem estática nominal e concorrência CSP. Compare o posicionamento:

Go Java/C# Python/Node Rust
Tempo de build segundos dezenas de s — minutos
Runtime no deploy nenhum (binário estático) JVM/CLR interpretador nenhum
Gerência de memória GC (pausas sub-ms) GC (tunável, maior) GC + GIL sem GC, ownership
Concorrência goroutine + channel thread / virtual thread event loop async + Send/Sync
Curva dias semanas dias meses
Expressividade baixa por escolha média alta alta

O nicho onde Go domina é bem definido: serviços de rede, CLIs e infraestrutura — Docker, Kubernetes, Terraform, Prometheus, etcd e o próprio Kafka Connect em Go (sarama/franz-go) vivem aí. Não é a linguagem para cálculo numérico, UI ou ciência de dados.

Trade-offs

O custo que você sente no primeiro dia: verbosidade. Um if err != nil a cada chamada, três linhas para o que outras linguagens fazem em uma. A defesa honesta não é "você se acostuma" — é que o caminho de erro fica junto do caminho de sucesso, e por isso ninguém esquece dele. O custo é real e a compensação é real.

O custo que você sente no primeiro mês: o zero value. Go não tem null safety. Toda variável nasce com valor zero — 0, "", nil, struct com campos zerados. Isso elimina uma classe de bugs (variável não inicializada) e cria outra: não há como distinguir "não informado" de "informado como zero". Daí a epidemia de *int e sql.NullString em qualquer código que fale com banco ou JSON.

O custo que você sente no primeiro ano: pouca abstração. Sem tipos-soma, um error pode ser qualquer coisa; sem enums, uma constante de status é só um string ou int disfarçado; sem imutabilidade na linguagem, "não modifique este slice" é comentário, não garantia. Ver 4. Mutabilidade.

O trade-off central: Go aposta que o tempo total gasto lendo código supera em muito o tempo gasto escrevendo. Se essa aposta vale para o seu contexto (time grande, serviço longevo, rotatividade), o preço é barato. Se você é um dev sozinho num protótipo, você só está pagando.

Exemplo prático

Um servidor HTTP completo, sem uma única dependência externa:

package main

import (
	"encoding/json"
	"log/slog"
	"net/http"
	"time"
)

type health struct {
	Status string    `json:"status"`
	Time   time.Time `json:"time"`
}

func main() {
	mux := http.NewServeMux()

	// Roteamento por método e wildcard: stdlib, desde o Go 1.22.
	mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		if err := json.NewEncoder(w).Encode(health{"ok", time.Now()}); err != nil {
			slog.Error("encode falhou", "err", err)
		}
	})

	srv := &http.Server{
		Addr:              ":8080",
		Handler:           mux,
		ReadHeaderTimeout: 5 * time.Second, // sem isso, Slowloris derruba o processo
	}
	slog.Info("subindo", "addr", srv.Addr)
	if err := srv.ListenAndServe(); err != nil {
		slog.Error("servidor caiu", "err", err)
	}
}

Três coisas dessa amostra explicam a adoção do Go em infraestrutura:

  1. Zero dependências. Roteador, JSON, log estruturado (log/slog, Go 1.21+) e servidor HTTP são biblioteca padrão. Nada para atualizar por CVE de terceiro.
  2. O deploy é um arquivo. CGO_ENABLED=0 go build gera um binário estático que roda num contêiner FROM scratch de ~10 MB. Ver 5. Go em Produção.
  3. A tag json:"status" mostra o estilo: metadados por struct tag, resolvidos por reflexão — barato de escrever, e a razão de o encoding/json ter sido reescrito no Go 1.27. Ver 4. Versões, Módulos e Compatibilidade.

Aprofundamento aplicado

Fronteiras de pacote e dependências

Em um serviço de pedidos, separar pedido, httpapi e postgres pode manter a regra de reserva independente do transporte e do banco. A dependência deve acompanhar a necessidade: a regra recebe uma operação de persistência, enquanto o adaptador conhece SQL.

Uma interface é útil quando existe comportamento a substituir ou uma fronteira que merece um contrato. Criar uma interface para cada struct aumenta navegação sem necessariamente reduzir acoplamento. Também é possível começar com funções e tipos concretos.

Cenário: a regra de desconto precisa ser exercitada sem abrir conexão. Se ela exige inicializar servidor HTTP e banco, a montagem está misturada ao cálculo. Extraia uma função que recebe os dados necessários e devolve o resultado ou erro.

Exercício: implemente o mesmo cálculo em um comando de terminal e em um handler. Resultado esperado: os dois usam a mesma regra; apenas leitura da entrada, resposta e tratamento do erro pertencem aos adaptadores.

Referência: organização de módulos e pacotes.

Relacionado


Parte de MOC Go · go.dev/doc

Buscar

Busca por título, seção e texto das notas