100/100 Dias de Golang - Fim!
Table of Contents
#
100/100
Chegamos! Esse é o post 101, porque, como eu disse no dia 50, um blog de programação tinha que começar no índice 0.
No dia 0 a ideia era simples: “fazer um post todo dia e me forçar a estudar, nem que seja um pouco, de Golang todo dia”. E no dia 50 eu prometi que, no dia 100, faria de novo o índice de todos os posts agrupados por assunto. Promessa é dívida, então este post tem três partes: como foi a jornada, os números da série (calculados, claro, com um programa em Go) e o índice completo.
#
Não foram 100 dias seguidos
Sendo honesto: “100 dias” virou mais um nome do que uma contagem. Olhando as datas dos posts, a série teve três fases bem claras:
- Dias 0 a 83 (12 de março a 3 de junho): um post por dia, sem falhar. Foi a fase mais intensa: a base da linguagem, concorrência, testes, web, o projeto da previsão do tempo, o jogo em UDP, a TUI, o linter…
- Dias 84 a 88 (junho e começo de julho): o ritmo diminuiu. Testcontainers, Fyne, POO, Goose e o bot do Discord saíram com alguns dias de intervalo entre eles.
- Dias 89 a 100 (6 a 17 de dezembro): depois de cinco meses parado, voltei para terminar, de novo com um post por dia.
O resultado: 280 dias entre o primeiro e o último post. Não é o que estava no plano, mas a série terminou, e acho que isso vale mais do que a contagem perfeita.
#
Do básico à produção
Relendo os posts em ordem, dá para ver a série mudando de assunto conforme o conhecimento foi aumentando.
Os fundamentos. Os primeiros posts são sobre a linguagem em si: tipos, slices, maps, structs, funções, interfaces, ponteiros, generics. Coisas que parecem simples, mas que têm detalhes importantes, como a diferença entre o tamanho e a capacidade de um slice, ou por que a ordem de um map muda a cada execução.
Concorrência e testes. Depois vieram as goroutines, os channels, o select e os mutexes, que são o motivo de muita gente escolher Go. E uma sequência inteira de testes: testes em tabela, benchmark, fuzzing, Testify, mocks, Testcontainers. O Go já vem com quase tudo pronto para testar, e isso faz diferença.
Construindo coisas. A partir do dia 40, a série ficou mais prática: CLI com Cobra, servidores web com a biblioteca padrão e com Gin, uma aplicação completa de previsão do tempo, um chat em TCP, um jogo multiplayer em UDP, um jogo no terminal com Bubble Tea, um linter que analisa código Go, um mini servidor de CI/CD, gráficos, interfaces gráficas e um bot para o Discord.
O ecossistema. No meio disso, várias bibliotecas e ferramentas: Zap, GORM, Colly, Faker, lo, go-echarts, Air, Docker…
Indo para produção. Os últimos posts fecharam com o que separa um programa que funciona de um programa pronto para produção: context, graceful shutdown, padrões de concorrência, profiling com pprof, como funciona o garbage collector, logs estruturados, métricas e tracing, SQL sem ORM, autenticação, gRPC, iterators e embed.
Um assunto foi aparecendo em quase todos os posts do final: o context.Context. O graceful shutdown depende dele, os logs pegam o request ID dele, os spans do OpenTelemetry passam por ele, as queries do banco são canceladas por ele e o gRPC envia o deadline através dele. Se eu tivesse que escolher um único conceito para recomendar estudar com calma depois da base, seria esse.
#
Os números
No dia 50 eu criei um programa para gerar o índice, porque copiar 50 títulos à mão não dava. Agora, com 101 posts, é a vez de uma versão melhorada, usando várias coisas que aprendi no caminho.
package main
import (
"bufio"
"fmt"
"io/fs"
"iter"
"log"
"os"
"path"
"regexp"
"slices"
"strings"
"time"
)
type Post struct {
Arquivo string
Titulo string
Data time.Time
Rascunho bool
Palavras int
BlocosGo int
LinhasDeGo int
}
var nomeDoPost = regexp.MustCompile(`^\d{3}-golang\.pt\.md$`)
// Posts percorre a pasta e entrega cada post da série já processado
func Posts(fsys fs.FS) iter.Seq2[Post, error] {
return func(yield func(Post, error) bool) {
err := fs.WalkDir(fsys, ".", func(caminho string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() || !nomeDoPost.MatchString(d.Name()) {
return nil
}
post, err := lerPost(fsys, caminho)
if !yield(post, err) {
return fs.SkipAll
}
return nil
})
if err != nil {
yield(Post{}, err)
}
}
}
func lerPost(fsys fs.FS, caminho string) (Post, error) {
arquivo, err := fsys.Open(caminho)
if err != nil {
return Post{}, err
}
defer arquivo.Close()
post := Post{Arquivo: path.Base(caminho)}
scanner := bufio.NewScanner(arquivo)
scanner.Buffer(make([]byte, 1024*1024), 1024*1024)
separadores := 0
dentroDoCodigo, codigoGo := false, false
for scanner.Scan() {
linha := scanner.Text()
// front matter entre as duas linhas "---"
if separadores < 2 {
if linha == "---" {
separadores++
continue
}
chave, valor, _ := strings.Cut(linha, ":")
valor = strings.Trim(strings.TrimSpace(valor), `'"`)
switch strings.ToLower(chave) {
case "title":
post.Titulo = valor
case "date":
post.Data, _ = time.Parse(time.DateOnly, valor)
case "draft":
post.Rascunho = strings.EqualFold(valor, "true")
}
continue
}
if strings.HasPrefix(linha, "```") {
if !dentroDoCodigo {
codigoGo = strings.TrimPrefix(linha, "```") == "go"
if codigoGo {
post.BlocosGo++
}
}
dentroDoCodigo = !dentroDoCodigo
continue
}
if dentroDoCodigo {
if codigoGo && strings.TrimSpace(linha) != "" {
post.LinhasDeGo++
}
continue
}
post.Palavras += len(strings.Fields(linha))
}
return post, scanner.Err()
}
func main() {
if len(os.Args) < 2 {
log.Fatal("uso: go run . <pasta dos posts>")
}
var posts []Post
for post, err := range Posts(os.DirFS(os.Args[1])) {
if err != nil {
log.Fatal(err)
}
if !post.Rascunho {
posts = append(posts, post)
}
}
slices.SortFunc(posts, func(a, b Post) int { return strings.Compare(a.Arquivo, b.Arquivo) })
var palavras, blocos, linhas int
for _, p := range posts {
palavras += p.Palavras
blocos += p.BlocosGo
linhas += p.LinhasDeGo
}
maior := slices.MaxFunc(posts, func(a, b Post) int { return a.Palavras - b.Palavras })
maisCodigo := slices.MaxFunc(posts, func(a, b Post) int { return a.LinhasDeGo - b.LinhasDeGo })
primeiro, ultimo := posts[0], posts[len(posts)-1]
fmt.Printf("posts publicados: %d\n", len(posts))
fmt.Printf("primeiro post: %s\n", primeiro.Data.Format(time.DateOnly))
fmt.Printf("último post: %s\n", ultimo.Data.Format(time.DateOnly))
fmt.Printf("dias corridos: %.0f\n", ultimo.Data.Sub(primeiro.Data).Hours()/24)
fmt.Printf("palavras (texto): %d\n", palavras)
fmt.Printf("blocos de Go: %d\n", blocos)
fmt.Printf("linhas de Go: %d\n", linhas)
fmt.Printf("maior post: %s (%d palavras)\n", maior.Titulo, maior.Palavras)
fmt.Printf("mais código: %s (%d linhas)\n", maisCodigo.Titulo, maisCodigo.LinhasDeGo)
fmt.Println()
for _, p := range posts {
fmt.Printf("- [%s](./%s)\n", p.Titulo, p.Arquivo)
}
}
Comparando com o programa do dia 50, que usava filepath.Walk e pegava só o título da segunda linha:
os.DirFS+fs.WalkDir: o programa lê de umfs.FS, a mesma interface doembeddo dia 99. Num teste, dá para passar umfstest.MapFScom posts falsos em vez da pasta de verdade;- Um iterator (
iter.Seq2[Post, error]) esconde a caminhada pelas pastas: omainsó faz umfor range, igual ao exemplo de paginação. E se omainparasse no meio, ofs.SkipAllinterromperia a caminhada; - O front matter é lido de verdade, com
strings.Cut, e os rascunhos (draft: true) ficam de fora. Tenho alguns rascunhos parados que não contam como post; - O
bufio.Scannercom buffer maior: o buffer padrão é de 64KB por linha, e uma linha maior que isso faria oScanparar com erro; slices.SortFunceslices.MaxFuncpara ordenar e encontrar os maiores posts, em vez de loops manuais.
Rodando na pasta dos posts:
go run . content/posts/golang
posts publicados: 101
primeiro post: 2025-03-12
último post: 2025-12-17
dias corridos: 280
palavras (texto): 39945
blocos de Go: 457
linhas de Go: 6545
maior post: 100/100 Dias de Golang - Fim! (2205 palavras)
mais código: 99/100 Dias de Golang - Iterators e embed (314 linhas)
As “palavras” são as do texto, sem contar os blocos de código, e as “linhas de Go” contam só as linhas não vazias dentro de blocos marcados como Go. E sim, o maior post da série é este aqui, mas é trapaça: são os 101 links do índice lá embaixo.
#
O que fica
Algumas coisas que levo da série:
- A biblioteca padrão é enorme e boa. Servidor HTTP, JSON, testes, benchmarks, profiling, logs estruturados, templates,
embed… Muitas vezes a primeira pergunta deveria ser “a biblioteca padrão já faz isso?”, e a resposta costuma ser sim; - Simplicidade dá trabalho. Go tem poucas funcionalidades de propósito. No começo isso parece limitação (cadê o
mape ofilter?), mas o código fica fácil de ler, inclusive o código dos outros; - Erros são valores. Tratar o
errem cada chamada parece repetitivo, mas força a pensar no que fazer quando algo dá errado, em vez de deixar uma exceção subir sem ninguém ver; - Escrever ensina. Para explicar um assunto num post, eu precisava entender de verdade, rodar os exemplos e conferir as saídas. Muitas das coisas que achava que sabia, só aprendi direito na hora de escrever.
#
E agora?
A série acabou, mas o blog não. Ficaram alguns rascunhos pelo caminho, sobre arquitetura limpa em Go e testes com WireMock, que podem virar posts. E tem um rascunho sobre Sliding Window que pode ser o começo de uma série sobre estruturas de dados e algoritmos, implementados em Go.
Obrigado a quem acompanhou, nem que seja um post. Até a próxima!
#
Todos os posts
O índice completo, agrupado por assunto como no dia 50 (inspirado no roadmap.sh):
##
Fundamentos
- 0/100 Dias de Golang - Início
- 1/100 Dias de Golang - Tipos Básicos
- 2/100 Dias de Golang - Tipos Compostos - Arrays
- 3/100 Dias de Golang - Tipos Compostos - Slices
- 4/100 Dias de Golang - Tipos Compostos - Maps
- 5/100 Dias de Golang - Tipos Compostos - Structs
- 6/100 Dias de Golang - If, For, Switch e GoTo
- 7/100 Dias de Golang - Prática
- 8/100 Dias de Golang - Funções
- 9/100 Dias de Golang - Structs 2
- 12/100 Dias de Golang - Pacotes e Módulos
- 33/100 Dias de Golang - make()
- 35/100 Dias de Golang - Error Handling
- 37/100 Dias de Golang - Strings
- 73/100 Dias de Golang - Panic, Defer e Recover
- 74/100 Dias de Golang - Panic e Recover
- 86/100 Dias de Golang - Conceitos de POO
##
Indo mais fundo
- 10/100 Dias de Golang - Interfaces
- 11/100 Dias de Golang - Ponteiros
- 13/100 Dias de Golang - Generics
- 21/100 Dias de Golang - JSON
- 22/100 Dias de Golang - Biblioteca “time
- 23/100 Dias de Golang - Manipulação de arquivos
- 24/100 Dias de Golang - Continuação sobre a biblioteca “time
- 68/100 Dias de Golang - Precisão quase infinita com math/big
- 69/100 Dias de Golang - Ordenação com o pacote sort
- 70/100 Dias de Golang - Manipulação de caminhos com path/filepath
- 81/100 Dias de Golang - Reflect
- 93/100 Dias de Golang - Stack, Heap, Escape Analysis e Garbage Collector
- 99/100 Dias de Golang - Iterators e embed
##
Concorrência
- 17/100 Dias de Golang - Concorrência e Goroutines
- 18/100 Dias de Golang - Goroutines, WaitGroup, Race condition e Mutex
- 19/100 Dias de Golang - Channels
- 20/100 Dias de Golang - Exemplo de goroutines, mutex e channels
- 34/100 Dias de Golang - Como o Runtime do Golang Gerencia Goroutines
- 36/100 Dias de Golang - Select Goroutines
- 89/100 Dias de Golang - Context
- 91/100 Dias de Golang - Padrões de Concorrência - Worker Pool, Fan-out/Fan-in e errgroup
##
Testes
- 25/100 Dias de Golang - Testes em Golang
- 26/100 Dias de Golang - Testes em batch, AAA e Cobertura de Código
- 27/100 Dias de Golang - Testes de benchmark
- 28/100 Dias de Golang - Testes em Go com Fuzzing
- 29/100 Dias de Golang - Testes com Testify
- 30/100 Dias de Golang - Testes - Exemplo Prático
- 61/100 Dias de Golang - Testes - Mocks
- 62/100 Dias de Golang - Testes - Mockgen
- 63/100 Dias de Golang - Gerando dados com Faker
- 84/100 Dias de Golang - Testes de Integração em Go com Testcontainers
##
Web e APIs
- 14/100 Dias de Golang - Requests para APIs Externas - Parte 1
- 15/100 Dias de Golang - Requests para APIs Externas - Parte 2
- 16/100 Dias de Golang - Requests para APIs Externas - Parte 3
- 42/100 Dias de Golang - Webservers em Go com a Biblioteca Padrão
- 43/100 Dias de Golang - Webservers com Gin
- 97/100 Dias de Golang - Autenticação com JWT e Middlewares no Gin
- 98/100 Dias de Golang - gRPC e Protocol Buffers
##
Redes e tempo real
- 51/100 Dias de Golang - Criando um servidor TCP em Go
- 52/100 Dias de Golang - Chat Golang
- 53/100 Dias de Golang - Websockets com Melody
- 54/100 Dias de Golang - Sockets em UDP
##
Banco de dados
- 31/100 Dias de Golang - GORM
- 87/100 Dias de Golang - Migrações de Banco de Dados em Produção com Goose
- 96/100 Dias de Golang - SQL sem ORM com database/sql e sqlc
##
Logging e observabilidade
- 38/100 Dias de Golang - Logging
- 39/100 Dias de Golang - Logging com Zap
- 92/100 Dias de Golang - Profiling com pprof
- 94/100 Dias de Golang - Logging estruturado com log/slog
- 95/100 Dias de Golang - Métricas com Prometheus e Tracing com OpenTelemetry
##
CLIs, TUIs e interfaces gráficas
- 40/100 Dias de Golang - Cobra CLI
- 41/100 Dias de Golang - Gerenciando Configurações
- 64/100 Dias de Golang - Iniciando uma TUI - Terminal User Interface
- 85/100 Dias de Golang - Interfaces Gráficas com Fyne
##
Deploy e produção
- 75/100 Dias de Golang - Docker e Golang
- 76/100 Dias de Golang - Hot Reload com Air
- 90/100 Dias de Golang - Graceful Shutdown
##
Ecossistema e ferramentas
- 32/100 Dias de Golang - Caminhos para estudar Golang
- 67/100 Dias de Golang - Webscrapping com Colly
- 71/100 Dias de Golang - Criando um linter básico com golang
- 72/100 Dias de Golang - Criando um linter básico com golang - Buscando ifs dentro de ifs
- 82/100 Dias de Golang - Biblioteca lo
- 83/100 Dias de Golang - Criação de gráficos com go-echarts
##
Projetos
- 44/100 Dias de Golang - Aplicação para buscar previsão do tempo - Parte 1
- 45/100 Dias de Golang - Aplicação para buscar previsão do tempo - Parte 2
- 46/100 Dias de Golang - Aplicação para buscar previsão do tempo - Parte 3
- 47/100 Dias de Golang - Aplicação para buscar previsão do tempo - Parte 4
- 48/100 Dias de Golang - Aplicação para buscar previsão do tempo - Parte 5
- 49/100 Dias de Golang - Aplicação para buscar previsão do tempo - Parte 6
- 55/100 Dias de Golang - Jogo em UDP - Parte 1
- 56/100 Dias de Golang - Jogo em UDP - Parte 2
- 57/100 Dias de Golang - Jogo em UDP - Parte 3
- 58/100 Dias de Golang - Jogo em UDP - Parte 4
- 59/100 Dias de Golang - Jogo em UDP - Parte 5
- 60/100 Dias de Golang - Jogo em UDP - Parte 6
- 65/100 Dias de Golang - Jogo usando bubbletea e lipgloss
- 66/100 Dias de Golang - Jogo usando bubbletea e lipgloss - Parte 2
- 77/100 Dias de Golang - Mini Servidor de CI/CD – Parte 1 - Visão Geral do Projeto
- 78/100 Dias de Golang - Mini Servidor de CI/CD – Parte 2 - Modelagem e Banco de Dados com GORM
- 79/100 Dias de Golang - Mini Servidor de CI/CD – Parte 3 - Implementação da API com Gin
- 80/100 Dias de Golang - Mini Servidor de CI/CD – Parte 4 - Adicionando testes ao repositório!
- 88/100 Dias de Golang - Bot para discord