Ispirato al classico articolo di Tom Dalling, questo post esplora come sovra-ingegnerizzare all’estremo il semplice problema di FizzBuzz utilizzando Go.
Partiremo dall’implementazione più elementare e ne faremo progressivamente il refactoring per esplorare le funzionalità del linguaggio Go, dalle istruzioni condizionali di base fino alle funzioni di ordine superiore e alla concorrenza.
L’output deve essere la classica sequenza FizzBuzz da 1 a 15:
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
Iniziamo con l’approccio più diretto. Porta a termine il lavoro, ma richiede di duplicare la logica del modulo sia per il 3 che per il 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)
}
}
}
Per evitare di duplicare i numeri 3 e 5 e le stringhe "Fizz" e "Buzz", possiamo costruire la stringa risultante in modo sequenziale. Se la stringa rimane vuota dopo i nostri controlli, stampiamo semplicemente il numero.
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)
}
}
A questo punto, estraiamo completamente le regole.
Nota: L’articolo originale utilizzava una map[int]string per questo scopo. Tuttavia, Go randomizza deliberatamente l’ordine di iterazione delle mappe. Usare una mappa significa che potresti occasionalmente stampare “BuzzFizz” invece di “FizzBuzz”! Per risolvere il problema, utilizziamo una slice di struct per garantire l’ordine di esecuzione.
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)
}
}
Invece di passare dati, cosa succederebbe se passassimo un comportamento? Possiamo definire un tipo Trigger come una funzione che modifica un puntatore a stringa in base all’iterazione corrente.
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)
}
}
Scrivere per esteso quelle funzioni anonime è ripetitivo. Possiamo applicare il principio DRY scrivendo una funzione factory che restituisce una funzione. Questo sfrutta le closure in 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 funge da factory, restituendo una funzione Trigger personalizzata
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)
}
}
Infine, rendiamolo idiomatico in Go sfruttando la concorrenza. Invece di accumulare i risultati in una slice e restituirli alla fine, possiamo elaborare i numeri in una goroutine e inviare il flusso dei risultati al thread principale tramite un canale (channel).
Nota la firma chan<- string in run. Questo limita correttamente la funzione al solo invio di dati sul canale, un’ottima pratica in 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 // Invia il risultato in streaming
}
close(out) // Chiudi sempre il canale al termine
}
func create(number int, word string) Trigger {
return func(res *string, i int) {
if i%number == 0 {
*res += word
}
}
}
func main() {
// Crea un canale non bufferizzato
results := make(chan string)
// Avvia la generazione in una goroutine in background
go run(15, results, []Trigger{
create(3, "Fizz"),
create(5, "Buzz"),
func(res *string, i int) {
if *res == "" {
*res = fmt.Sprint(i)
}
},
})
// Itera sul canale per leggere i valori man mano che arrivano
for word := range results {
fmt.Println(word)
}
}
Sovra-ingegnerizzando un problema semplice come FizzBuzz, abbiamo passato in rassegna cicli strutturali, regole guidate dai dati (e il motivo per cui l’ordine di iterazione delle mappe è importante!), closure, funzioni di ordine superiore e, infine, goroutine e canali. È un ottimo promemoria di quanti modi diversi esistano per risolvere un singolo problema in Go!
Scarica l'edizione offline di Lo Stile di Vita di Kaue—una guida pratica per ottimizzare 🍏 Salute, 💰 Ricchezza, ❤️ Amore e 🧘 Significato.
Ricevi una notifica quando pubblico nuovi saggi, riassunti di libri e guide pratiche per vivere bene. Niente spam—annulla l'iscrizione in qualsiasi momento.
Hai una domanda, una prospettiva o un suggerimento? Condividi i tuoi pensieri qui sotto. I commenti utilizzano GitHub Discussions (senza pubblicità e senza spam)—clicca su Accedi con GitHub qui sotto per commentare o reagire (creare un account GitHub gratuito richiede meno di un minuto).
Aggiornato il 11 ott 2026.
AVVERTENZA LEGALE: Il contenuto di questo sito ha scopo esclusivamente informativo ed educativo e non costituisce consulenza medica, psicologica, finanziaria, di investimento, fiscale o legale. Le idee, la selezione e le opinioni qui presentate sono esclusivamente mie e non rappresentano quelle del mio datore di lavoro. I riassunti di libri, conferenze e articoli sono commenti educativi indipendenti e rimangono proprietà intellettuale dei rispettivi autori. Strumenti di IA possono essere utilizzati per assistere nella revisione, formattazione e sintesi; sebbene venga compiuto ogni sforzo per garantire l'accuratezza, possono verificarsi errori. PRIVACY: Questo sito utilizza servizi di terze parti per l'iscrizione facoltativa alla newsletter (follow.it), i commenti (giscus / GitHub Discussions) e i contenuti multimediali incorporati (YouTube), che possono trattare gli indirizzi IP e i cookie dei visitatori in conformità con le rispettive informative sulla privacy.