Estudando e se aprofundando em Go

Estudando e se aprofundando em Go

Por que aprender Go hoje: a linguagem de Docker e Kubernetes, o que ela resolveu que C++, Java e Python deixavam em aberto, o que mudou nas versões recentes e o roteiro completo da série, dos fundamentos à concorrência, aos bancos de dados e ao deploy em nuvem.
Golang

• • 13 min de leitura

Existe uma pergunta legítima que qualquer desenvolvedor deve fazer antes de investir meses aprendendo uma linguagem nova: vale o esforço? Por que estudar Go hoje?

No caso do Go, a resposta é objetiva. Docker, Kubernetes, Terraform, Prometheus, o CLI do GitHub, o Caddy, o CockroachDB — toda a espinha dorsal da infraestrutura moderna da internet foi escrita em Go. Não por acidente, mas porque a linguagem resolve com elegância um problema que outras linguagens resolvem mal: ser ao mesmo tempo simples de escrever, fácil de manter e rápida o suficiente para produção de alto tráfego.

Empresas como Uber, Cloudflare, Twitch, Dropbox e o próprio Google colocaram Go no centro de seus sistemas mais críticos. No Brasil, o ecossistema cresce a cada ano — e profissionais que dominam a linguagem ainda são escassos em relação à demanda.

O problema que Go resolve

No final dos anos 2000, engenheiros do Google conviviam com uma tensão constante: C++ era rápido mas dolorosamente complexo e lento para compilar. Java era produtivo mas verboso e pesado. Python era ágil mas insuficiente para concorrência e performance em escala. Cada escolha implicava um sacrifício relevante.

Go nasceu dessa insatisfação — não como exercício acadêmico, mas como resposta prática a problemas reais enfrentados por pessoas que construíam sistemas usados por bilhões de usuários. Essa origem pragmática está impressa em cada decisão de design da linguagem: sem hierarquia de classes, sem sobrecarga de operadores, sem exceções, apenas 25 palavras-chave, compilação em segundos e concorrência como cidadã de primeira classe.

O resultado é uma linguagem onde um desenvolvedor experiente consegue ler código que nunca viu antes e entendê-lo rapidamente — e onde um time inteiro formata o código da mesma forma, sem discussão, porque o gofmt, que vem com a linguagem, define um formato só e não tem opções de estilo. O compilador cuida de outra parte da disciplina: import ou variável local sem uso não é aviso, é erro de compilação.

O que é Go e como ele funciona?

Go é uma linguagem compilada, estaticamente tipada, com gerenciamento automático de memória via garbage collector e suporte nativo à concorrência por meio de goroutines e channels. Foi criada por Robert Griesemer, Rob Pike e Ken Thompson — três engenheiros com histórico direto no Unix, no UTF-8 e no V8, o motor JavaScript do Chrome — e lançada como open source em novembro de 2009.

Diferente de linguagens interpretadas, Go compila direto para binário nativo. Um programa Go vira um executável único, sem dependências externas, que roda em Linux, Windows ou macOS com o mesmo comando. Isso torna a distribuição de ferramentas e serviços extraordinariamente simples.

Com o Go 1.18, a linguagem ganhou Generics — a adição mais aguardada pela comunidade em anos. O Go 1.21 trouxe um pacote nativo de logging estruturado, o slog, e o 1.22 desarmou a armadilha mais conhecida da linguagem: cada volta do for passou a ter a sua própria variável de laço. Um laço de três voltas que guarda closures e só as chama no fim imprime 0 1 2 — mas só se o go.mod declarar go 1.22 ou mais. Com go 1.21 ali, o mesmo programa imprime 3 3 3, ainda que compilado pelo Go 1.27: quem manda é a diretiva do módulo, não a versão instalada. A linguagem evolui com cautela, mantendo uma promessa que poucos fazem: código escrito para Go 1.0 em 2012 compila e funciona corretamente nas versões atuais.

Onde Go é usado hoje?

Infraestrutura e DevOps: Docker, Kubernetes, Terraform e Prometheus definiram Go como a linguagem padrão para ferramentas de infraestrutura cloud. Se você trabalha com DevOps ou SRE, vai encontrar Go em praticamente todo lugar.

Backends e APIs de alto tráfego: Frameworks como Gin, Echo e Fiber tornaram Go uma escolha popular para APIs RESTful que precisam responder a milhões de requisições com latência baixa e consumo de memória previsível.

Microsserviços: A combinação de binários estáticos, startup instantâneo e concorrência eficiente faz de Go uma escolha natural para arquiteturas baseadas em contêineres e funções serverless.

Ferramentas de linha de comando: A capacidade de compilar um binário sem dependências torna Go ideal para CLIs distribuídas — o usuário baixa um arquivo e executa, sem instalar runtime.

Segurança e redes: Ferramentas como o servidor DNS CoreDNS, proxies reversos e scanners de segurança são frequentemente escritos em Go pela combinação de performance e facilidade de manipulação de pacotes de rede.

Por que esta série é diferente

A maioria dos tutoriais de Go para no básico. Variáveis, loops, funções — e então um exemplo de servidor HTTP de dez linhas apresentado como "você já sabe fazer APIs em Go".

O problema é que entre escrever um servidor de dez linhas e construir um sistema real — com tratamento de erros consistente, concorrência segura, testes confiáveis, banco de dados, autenticação e deploy — existe um abismo que quase nenhum curso atravessa com você.

Esta série foi planejada para percorrer esse caminho inteiro. Você vai entender não apenas como escrever Go, mas por que Go funciona do jeito que funciona — e como essas decisões de design se traduzem em sistemas mais simples de manter e mais difíceis de quebrar. Do primeiro fmt.Println até arquitetura limpa, gRPC e deploy em cloud, sem pular etapas.

O que você vai aprender

Módulo Tema
1 Fundamentos: sintaxe, tipos, controle de fluxo, funções e ponteiros
2 Coleções e structs: arrays, slices e maps
3 Métodos, interfaces e composição
4 Erros, pacotes, módulos, biblioteca padrão e testes
5 Concorrência: goroutines, channels, select, sync e context
6 Arquivos, JSON, HTTP, middlewares e consumo de APIs
7 Bancos de dados: database/sql, MySQL, SQLite, PostgreSQL, MongoDB e GORM
8 Ferramentas: CLI, logging, configuração, análise estática, documentação e profiling
9 Arquitetura, deploy, gRPC e o ecossistema

Para quem é esta série?

Para desenvolvedores que já têm alguma experiência com programação — em qualquer linguagem — e querem adicionar Go ao seu repertório com profundidade real. Para quem vem de Python ou PHP e quer entender como é trabalhar com uma linguagem compilada e estaticamente tipada sem abrir mão de produtividade. Para quem trabalha com infraestrutura e quer entender de dentro as ferramentas que usa todo dia. E para quem quer construir backends performáticos e confiáveis sem a complexidade de C++ ou Rust.

Go tem uma curva de aprendizado incomum: as primeiras horas são simples, mas dominar seus padrões idiomáticos — a forma Go de resolver problemas — exige tempo e exposição a código real. É exatamente isso que esta série oferece.

Fontes e leituras recomendadas

Oficiais

  • A Tour of Go — O tutorial interativo oficial, com exercícios executáveis no navegador. É o caminho mais curto para escrever o primeiro código.
  • Effective Go — O documento que define o que é código idiomático em Go — formatação, nomes, tratamento de erro, concorrência.
  • The Go Programming Language Specification — A especificação completa da linguagem. Curta o bastante para ser lida de ponta a ponta, o que é raro entre linguagens.

Prática

  • Go by Example — Exemplos comentados, um por conceito, do hello world às goroutines. Bom para consulta rápida.
  • pkg.go.dev — A documentação de todos os pacotes públicos, incluindo a biblioteca padrão. É onde se confere assinatura e comportamento.
  • The Go Blog — Artigos dos próprios autores da linguagem explicando decisões de projeto e recursos novos a cada versão.

Exercícios

Exercício 1

Um time atualiza o Go instalado nas máquinas e no CI para a 1.27, na esperança de se livrar de um defeito antigo: um laço que cria closures e as chama depois imprime 3 3 3 em vez de 0 1 2. Depois da atualização, o defeito continua lá. O que ficou faltando, e por que a linguagem foi desenhada para exigir esse passo?

Ver resposta

✓ Resposta: Faltou mudar a diretiva go do go.mod. A variável de laço por iteração, que chegou no Go 1.22, só se aplica a módulos que declaram go 1.22 ou mais: com go 1.21 no arquivo, o mesmo programa compilado pelo Go 1.27 continua imprimindo 3 3 3, e basta trocar a linha para go 1.22 para ele passar a imprimir 0 1 2. O desenho é proposital. A promessa de compatibilidade do Go 1 diz que código antigo continua compilando e se comportando como antes nas versões novas, e essa mudança altera o significado de um programa que já compilava. Amarrá-la à versão que o próprio módulo declara resolve o impasse: quem não mexeu no go.mod mantém o comportamento que testou, e quem subiu a diretiva está dizendo, explicitamente, que aceita a semântica nova. O efeito prático para o time é que atualizar a ferramenta e atualizar a linguagem do projeto são dois passos distintos, e o segundo merece uma rodada de testes própria.

Exercício 2

O artigo diz que um programa Go vira um executável único, sem dependências externas. Confiando nisso, um colega compila um serviço que usa net/http na própria máquina Linux, copia o binário para uma imagem Docker FROM scratch e o contêiner não sobe: a mensagem fala em arquivo inexistente, embora o binário esteja lá. O que aconteceu, e como compilar para que funcione?

Ver resposta

✓ Resposta: O binário não era estático. Quando existe um compilador C na máquina, o cgo fica ligado por padrão (go env CGO_ENABLED responde 1), e o pacote net usa a libc do sistema para, entre outras coisas, resolver nomes. O resultado é um executável ligado dinamicamente: o ldd num binário assim, gerado pelo Go 1.27 num Linux com gcc, lista libc.so.6 e o carregador /lib64/ld-linux-x86-64.so.2. A imagem scratch não tem nenhum dos dois, e quando o kernel não encontra o carregador que o executável pede, a falha é reportada como arquivo inexistente, o que despista qualquer um. A solução é compilar com CGO_ENABLED=0 go build: o mesmo programa sai como not a dynamic executable no ldd, com o resolvedor de nomes escrito em Go, e roda numa imagem vazia. A frase do artigo vale para o caso comum, mas quem vai distribuir binário ou montar imagem mínima precisa conferir com ldd em vez de supor. Se o projeto depender de uma biblioteca em C, como um driver de SQLite baseado em cgo, desligar o cgo não é opção, e a imagem precisa trazer a libc.

Exercício 3

Uma desenvolvedora que vem do Python abre um PR com um import "os" que ela pretende usar no commit seguinte e uma variável auxiliar ainda não usada. O CI quebra e ela pergunta qual flag desliga "esses warnings" até terminar a funcionalidade. O que você responde?

Ver resposta

✓ Resposta: Que não são warnings e que não existe flag. Em Go, import e variável local sem uso são erros de compilação: o Go 1.27 recusa o programa com "os" imported and not used e declared and not used: x, e nenhum binário é gerado. A decisão é deliberada: import sobrando aumenta o tempo de build e cria dependência fantasma, e variável declarada e esquecida costuma ser sintoma de erro de lógica, como um resultado calculado e nunca conferido. Enquanto o código está em construção, há duas saídas honestas. A primeira é simplesmente não importar ainda o que não se usa, já que o goimports, que a maioria dos editores roda ao salvar, adiciona e remove imports sozinho. A segunda, para uma variável que precisa existir por um momento, é atribuí-la ao identificador vazio, _ = x, sabendo que isso tem de sair antes do merge. O que não se faz é deixar esses remendos no código que vai para a branch principal.

Exercício 4

Na primeira semana de um projeto em Go, o time abre uma discussão sobre indentar com tabs ou com quatro espaços e sobre a posição da chave de abertura. Um líder técnico encerra a conversa em uma frase. Qual é a frase, e o que o time precisa configurar no CI para que o assunto não volte?

Ver resposta

✓ Resposta: "Quem decide é o gofmt." Ele tem um formato só, sem arquivo de configuração nem opção de estilo: indenta com tab, alinha com espaço e reescreve a chave onde ela deve estar. A chave, aliás, nem é assunto de estilo: por causa da inserção automática de ponto e vírgula no fim da linha, pôr a { do if ou da func na linha de baixo é erro de compilação. O ponto que o time precisa entender é que o compilador não impõe a formatação: um arquivo com quatro espaços e parênteses cheios de espaços compila normalmente. Por isso a verificação vai para o CI: gofmt -l . lista os arquivos que não estão no formato, e o job falha se a lista não vier vazia. Com o editor rodando gofmt ou goimports ao salvar, ninguém nem chega a ver a falha.

Exercício 5

Alguém lê que Go não tem exceções e conclui que, sem try/catch, um erro inesperado derruba o servidor inteiro. Para se proteger, propõe envolver o corpo de cada handler HTTP num recover. O raciocínio procede? O que acontece de fato quando um handler do net/http entra em pânico?

Ver resposta

✓ Resposta: Procede só pela metade. Em Go, falha esperada, como arquivo que não existe ou requisição que expirou, é um valor do tipo error devolvido pela função, e quem chama decide o que fazer. O panic fica para o que não deveria acontecer, como escrever num map nil ou acessar índice fora do slice, e um pânico que ninguém recupera encerra o programa. Mas o net/http já recupera o pânico de cada requisição. Um handler que escreve num map nil gera no log do servidor uma linha http: panic serving …: assignment to entry in nil map com a pilha, o cliente dessa requisição recebe a conexão fechada (o http.Get devolve EOF, e não um 500), e a requisição seguinte é atendida normalmente. O recover em cada handler é, portanto, redundante para manter o servidor de pé. O que um middleware de recuperação acrescenta de útil é responder um 500 decente e registrar o erro no formato de log do projeto, uma vez só, na borda. Ele não substitui o tratamento de error dentro do código.

Comentários

Mais em Golang

Testes em Go: testing, tabelas de casos e benchmarks
Testes em Go: testing, tabelas de casos e benchmarks

Testes em Go: arquivos _test.go, Error e Fatal, tabelas de casos com t.Run…

Variáveis de ambiente e configuração com Viper
Variáveis de ambiente e configuração com Viper

Configuração em Go com os.LookupEnv, godotenv e Viper: arquivo YAML, variáveis…

Estruturas de controle: if, for e switch
Estruturas de controle: if, for e switch

Controle de fluxo em Go: if com inicialização, as formas do for, incluindo o…