💻 FizzBuzz con demasiado detalle: Una perspectiva en Go 🔗

Inspirado en el artículo clásico de Tom Dalling, esta publicación explora cómo aplicar una sobreingeniería desmesurada al sencillo problema de FizzBuzz utilizando Go.

Comenzaremos con la implementación más básica y la refactorizaremos progresivamente para explorar las características del lenguaje Go, desde condicionales básicos hasta funciones de orden superior y concurrencia.

📑 Tabla de Contenidos 🔗


🎯 El objetivo 🔗

La salida debe ser la secuencia clásica de FizzBuzz del 1 al 15:

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz

1️⃣ El enfoque ingenuo (Fácil) 🔗

Empezamos con el enfoque más directo. Cumple su cometido, pero requiere duplicar la lógica del módulo tanto para el 3 como para el 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 duplicación) 🔗

Para evitar duplicar los números 3 y 5 y las cadenas "Fizz" y "Buzz", podemos construir la cadena resultante de forma secuencial. Si la cadena sigue vacía después de nuestras comprobaciones, simplemente mostramos el 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 con Structs (Orientado a datos) 🔗

A continuación, extraigamos las reglas por completo.

Nota: El artículo original utilizaba un map[int]string para esto. Sin embargo, Go aleatoriza deliberadamente el orden de iteración de los mapas. ¡Usar un mapa significa que en ocasiones podrías imprimir "BuzzFizz" en lugar de "FizzBuzz"! Para solucionarlo, utilizamos un slice de structs que garantiza el orden de ejecución.

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 con funciones (Clausuras) 🔗

En lugar de pasar datos, ¿qué ocurre si pasamos comportamiento? Podemos definir un tipo Trigger como una función que modifica un puntero a una cadena en función de la iteración actual.

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️⃣ Funciones de orden superior (Una factoría de Triggers) 🔗

Escribir todas esas funciones anónimas resulta repetitivo. Podemos aplicar el principio DRY escribiendo una función factoría que devuelva una función. Esto aprovecha las clausuras (closures) en 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 actúa como una factoría, devolviendo una función Trigger personalizada
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️⃣ Pasando a la concurrencia (Canales) 🔗

Por último, hagámoslo en Go idiomático utilizando concurrencia. En lugar de acumular los resultados en un slice y devolverlos al final, podemos procesar los números en una goroutine y transmitir los resultados de vuelta al hilo principal a través de un canal.

Observa la firma chan<- string en run. Esto restringe correctamente la función para que solo pueda enviar datos al canal, lo cual es una buena práctica en 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 // Transmitir el resultado
	}
	close(out) // Cerrar siempre el canal al terminar
}

func create(number int, word string) Trigger {
	return func(res *string, i int) {
		if i%number == 0 {
			*res += word
		}
	}
}

func main() {
	// Crear un canal sin búfer
	results := make(chan string)
	
	// Iniciar la generación en una goroutine en segundo plano
	go run(15, results, []Trigger{
		create(3, "Fizz"),
		create(5, "Buzz"),
		func(res *string, i int) {
			if *res == "" {
				*res = fmt.Sprint(i)
			}
		},
	})
	
	// Iterar sobre el canal para leer los valores a medida que llegan
	for word := range results {
		fmt.Println(word)
	}
}

🏁 Conclusión 🔗

Al aplicar sobreingeniería a un problema tan sencillo como FizzBuzz, hemos recorrido bucles estructurales, reglas orientadas a datos (¡y por qué importa el orden de iteración de los mapas!), clausuras, funciones de orden superior y, finalmente, goroutines y canales. ¡Es un excelente recordatorio de cuántas formas distintas existen para resolver un mismo problema en Go!


📚 Descarga Mi Ebook Gratuito 🔗

Obtén la edición sin conexión de El Estilo de Vida de Kaue—una guía práctica para optimizar 🍏 Salud, 💰 Riqueza, ❤️ Amor y 🧘 Sentido.

El Estilo de Vida de Kaue

El Estilo de Vida de Kaue

📬 Suscríbete a Mi Boletín 🔗

Recibe notificaciones cuando publique nuevos ensayos, resúmenes de libros y guías prácticas sobre cómo vivir bien. Sin spam—cancela cuando quieras.


💬 Comentarios y Debate 🔗

¿Tienes alguna pregunta, perspectiva o sugerencia? Comparte tus ideas abajo. Los comentarios funcionan con GitHub Discussions (sin anuncios y sin spam)—haz clic en Iniciar sesión con GitHub abajo para comentar o reaccionar (crear una cuenta gratuita en GitHub toma menos de un minuto).



Actualizado el 11 oct 2026.

AVISO LEGAL: El contenido de este sitio tiene fines exclusivamente informativos y educativos y no constituye asesoramiento médico, psicológico, financiero, de inversión, fiscal ni legal. Las ideas, la selección y las opiniones aquí presentadas son exclusivamente mías y no representan las de mi empleador. Los resúmenes de libros, conferencias y artículos son comentarios educativos independientes y siguen siendo propiedad intelectual de sus respectivos autores. Se pueden utilizar herramientas de IA para ayudar en la edición, el formato y la síntesis; aunque se hace todo lo posible por garantizar la precisión, pueden producirse errores. PRIVACIDAD: Este sitio utiliza servicios de terceros para la suscripción opcional al boletín (follow.it), los comentarios (giscus / GitHub Discussions) y el contenido multimedia incrustado (YouTube), que pueden procesar las direcciones IP y las cookies de los visitantes de acuerdo con sus respectivas políticas de privacidad.