💻 FizzBuzz em Detalhes Demais: Uma Perspectiva em Go 🔗

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.

📑 Índice 🔗


🎯 O Objetivo 🔗

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

1️⃣ A Abordagem Ingênua (Fácil) 🔗

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)
		}
	}
}

2️⃣ Aplicando DRY (Menos Duplicação) 🔗

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)
	}
}

3️⃣ Parametrizado com Structs (Orientado a Dados) 🔗

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)
	}
}

4️⃣ Parametrizado com Funções (Closures) 🔗

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)
	}
}

5️⃣ Funções de Ordem Superior (Uma Fábrica de Triggers) 🔗

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)
	}
}

6️⃣ Tornando Concorrente (Canais) 🔗

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)
	}
}

🏁 Conclusão 🔗

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 Meu Ebook Gratuito 🔗

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.

O Estilo de Vida do Kaue

O Estilo de Vida do Kaue

📬 Assine Minha Newsletter 🔗

Receba notificações quando eu publicar novos ensaios, resumos de livros e guias práticos sobre como viver bem. Sem spam—cancele quando quiser.


💬 Comentários e Discussão 🔗

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.