Inspirado no artigo clássico de Tom Dalling, este post explora como aplicar uma engenharia absurdamente exagerada (over-engineering) ao simples problema do FizzBuzz usando Go.
Começaremos com a implementação mais básica e a refatoraremos progressivamente para explorar os recursos da linguagem Go, desde condicionais básicas até funções de ordem superior e concorrência.
A saída deve ser a clássica sequência FizzBuzz de 1 a 15:
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
Começamos com a abordagem mais direta. Ela cumpre o papel, mas exige duplicar a lógica do operador módulo (%) tanto para o 3 quanto para o 5.
package main
import "fmt"
func main() {
for i := 1; i <= 15; i++ {
if i%3 == 0 && i%5 == 0 {
fmt.Println("FizzBuzz")
} else if i%3 == 0 {
fmt.Println("Fizz")
} else if i%5 == 0 {
fmt.Println("Buzz")
} else {
fmt.Println(i)
}
}
}
Para evitar duplicar os números 3 e 5, bem como as strings "Fizz" e "Buzz", podemos construir a string resultante de forma sequencial. Se a string permanecer vazia após as verificações, simplesmente imprimimos o número.
package main
import "fmt"
func main() {
for i := 1; i <= 15; i++ {
res := ""
if i%3 == 0 {
res += "Fizz"
}
if i%5 == 0 {
res += "Buzz"
}
if res == "" {
res = fmt.Sprint(i)
}
fmt.Println(res)
}
}
Em seguida, vamos extrair completamente as regras.
Nota: O artigo original usou um map[int]string para isso. No entanto, o Go randomiza propositalmente a ordem de iteração de mapas. Usar um mapa significa que você poderia ocasionalmente imprimir “BuzzFizz” em vez de “FizzBuzz”! Para corrigir isso, usamos um slice de structs para garantir a ordem de execução.
package main
import "fmt"
type Rule struct {
Divisor int
Word string
}
func run(max int, rules []Rule) []string {
var all []string
for i := 1; i <= max; i++ {
res := ""
for _, rule := range rules {
if i%rule.Divisor == 0 {
res += rule.Word
}
}
if res == "" {
res = fmt.Sprint(i)
}
all = append(all, res)
}
return all
}
func main() {
rules := []Rule{
{3, "Fizz"},
{5, "Buzz"},
}
all := run(15, rules)
for _, word := range all {
fmt.Println(word)
}
}
Em vez de passar dados, e se passarmos comportamento? Podemos definir um tipo Trigger como uma função que modifica um ponteiro de string com base na iteração atual.
package main
import "fmt"
type Trigger func(res *string, i int)
func run(max int, triggers []Trigger) []string {
var all []string
for i := 1; i <= max; i++ {
res := ""
for _, trigger := range triggers {
trigger(&res, i)
}
all = append(all, res)
}
return all
}
func main() {
all := run(15, []Trigger{
func(res *string, i int) {
if i%3 == 0 {
*res += "Fizz"
}
},
func(res *string, i int) {
if i%5 == 0 {
*res += "Buzz"
}
},
func(res *string, i int) {
if *res == "" {
*res = fmt.Sprint(i)
}
},
})
for _, word := range all {
fmt.Println(word)
}
}
Escrever todas essas funções anônimas é repetitivo. Podemos enxugar isso escrevendo uma função fábrica (factory function) que retorna uma função, tirando proveito de closures em Go.
package main
import "fmt"
type Trigger func(res *string, i int)
func run(max int, triggers []Trigger) []string {
var all []string
for i := 1; i <= max; i++ {
res := ""
for _, trigger := range triggers {
trigger(&res, i)
}
all = append(all, res)
}
return all
}
// create acts as a factory, returning a custom Trigger function
func create(number int, word string) Trigger {
return func(res *string, i int) {
if i%number == 0 {
*res += word
}
}
}
func main() {
all := run(15, []Trigger{
create(3, "Fizz"),
create(5, "Buzz"),
func(res *string, i int) {
if *res == "" {
*res = fmt.Sprint(i)
}
},
})
for _, word := range all {
fmt.Println(word)
}
}
Por fim, vamos deixá-lo com a cara idiomática do Go utilizando concorrência. Em vez de acumular os resultados em um slice e retorná-los no final, podemos processar os números em uma goroutine e transmitir os resultados de volta para a thread principal por meio de um canal (channel).
Repare na assinatura chan<- string em run. Isso restringe corretamente a função a apenas enviar dados para o canal, uma ótima prática em Go.
package main
import "fmt"
type Trigger func(res *string, i int)
func run(max int, out chan<- string, triggers []Trigger) {
for i := 1; i <= max; i++ {
res := ""
for _, trigger := range triggers {
trigger(&res, i)
}
out <- res // Stream the result
}
close(out) // Always close the channel when finished
}
func create(number int, word string) Trigger {
return func(res *string, i int) {
if i%number == 0 {
*res += word
}
}
}
func main() {
// Create an unbuffered channel
results := make(chan string)
// Start the generation in a background goroutine
go run(15, results, []Trigger{
create(3, "Fizz"),
create(5, "Buzz"),
func(res *string, i int) {
if *res == "" {
*res = fmt.Sprint(i)
}
},
})
// Range over the channel to read values as they arrive
for word := range results {
fmt.Println(word)
}
}
Ao aplicar over-engineering em um problema simples como o FizzBuzz, passeamos por loops estruturais, regras orientadas a dados (e por que a ordem de iteração de mapas importa!), closures, funções de ordem superior e, finalmente, goroutines e canais. É um ótimo lembrete de quantas maneiras diferentes existem para resolver um único problema em Go!
Baixe a edição offline selecionada de O Estilo de Vida do Kaue—um guia prático para otimizar 🍏 Saúde, 💰 Riqueza, ❤️ Amor e 🧘 Sentido.
Receba notificações quando eu publicar novos ensaios, resumos de livros e guias práticos sobre como viver bem. Sem spam—cancele quando quiser.
Tem alguma pergunta, perspectiva ou sugestão? Compartilhe suas ideias abaixo. Os comentários usam o GitHub Discussions (sem anúncios e sem spam)—clique em Entrar com o GitHub abaixo para comentar ou reagir (criar uma conta gratuita no GitHub leva menos de um minuto).
Atualizado em 11 de out. de 2026.
AVISO LEGAL: O conteúdo deste site tem finalidade exclusivamente informativa e educacional e não constitui aconselhamento médico, psicológico, financeiro, de investimentos, tributário ou jurídico. As ideias, a curadoria e as opiniões aqui apresentadas são exclusivamente minhas e não representam as do meu empregador. Os resumos de livros, palestras e artigos são comentários educacionais independentes e permanecem propriedade intelectual de seus respectivos autores. Ferramentas de IA podem ser utilizadas para auxiliar na edição, formatação e síntese; embora todos os esforços sejam feitos para garantir a precisão, erros podem ocorrer. PRIVACIDADE: Este site utiliza serviços de terceiros para inscrição opcional em newsletter (follow.it), comentários (giscus / GitHub Discussions) e mídia incorporada (YouTube), que podem processar endereços IP e cookies dos visitantes de acordo com suas respectivas políticas de privacidade.