💻 FizzBuzz dans les moindres détails : une perspective en Go 🔗

Inspiré par l’article classique de Tom Dalling, ce billet explore comment sur-concevoir (over-engineer) à l’extrême le simple problème du FizzBuzz en utilisant Go.

Nous partirons de l’implémentation la plus basique et la refactoriserons progressivement afin d’explorer les fonctionnalités du langage Go, des conditions élémentaires jusqu’aux fonctions d’ordre supérieur et à la concurrence.

📑 Table des Matières 🔗


🎯 L’objectif 🔗

La sortie doit être la séquence FizzBuzz classique de 1 à 15 :

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

1️⃣ L’approche naïve (Facile) 🔗

Nous commençons par l’approche la plus directe. Elle fait le travail, mais elle oblige à dupliquer la logique du modulo pour 3 et pour 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️⃣ Appliquer le principe DRY (Moins de duplication) 🔗

Pour éviter de dupliquer les nombres 3 et 5, ainsi que les chaînes "Fizz" et "Buzz", nous pouvons construire la chaîne résultante de manière séquentielle. Si la chaîne reste vide après nos vérifications, nous affichons simplement le nombre.

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️⃣ Paramétré avec des structures (Piloté par les données) 🔗

Ensuite, extrayons complètement les règles.

Remarque : L’article original utilisait une map[int]string pour cela. Cependant, Go rend délibérément aléatoire l’ordre d’itération des maps. Utiliser une map signifie que vous pourriez occasionnellement afficher « BuzzFizz » au lieu de « FizzBuzz » ! Pour corriger cela, nous utilisons une tranche (slice) de structures afin de garantir l’ordre d’exécution.

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️⃣ Paramétré avec des fonctions (Fermetures / Closures) 🔗

Au lieu de passer des données, et si nous passions du comportement ? Nous pouvons définir un type Trigger comme une fonction qui modifie un pointeur de chaîne de caractères en fonction de l’itération actuelle.

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️⃣ Fonctions d’ordre supérieur (Une fabrique de déclencheurs) 🔗

Écrire ces fonctions anonymes est répétitif. Nous pouvons éviter cette répétition (DRY) en écrivant une fonction de fabrique (factory) qui retourne une fonction. Cela tire parti des fermetures (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 agit comme une fabrique, retournant une fonction Trigger personnalisée
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️⃣ Passer à la concurrence (Canaux / Channels) 🔗

Enfin, rendons ce code idiomatique en Go en utilisant la concurrence. Au lieu d’accumuler les résultats dans une tranche et de les retourner à la fin, nous pouvons traiter les nombres dans une goroutine et diffuser les résultats en continu vers le fil principal via un canal (channel).

Remarquez la signature chan<- string dans run. Cela restreint correctement la fonction pour qu’elle ne puisse qu’envoyer des données dans le canal, ce qui est une bonne pratique 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 // Diffuser le résultat
	}
	close(out) // Toujours fermer le canal une fois terminé
}

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

func main() {
	// Créer un canal sans tampon (unbuffered channel)
	results := make(chan string)
	
	// Démarrer la génération dans une goroutine en arrière-plan
	go run(15, results, []Trigger{
		create(3, "Fizz"),
		create(5, "Buzz"),
		func(res *string, i int) {
			if *res == "" {
				*res = fmt.Sprint(i)
			}
		},
	})
	
	// Parcourir le canal avec range pour lire les valeurs au fur et à mesure qu'elles arrivent
	for word := range results {
		fmt.Println(word)
	}
}

🏁 Conclusion 🔗

En sur-concevant un problème simple comme FizzBuzz, nous avons parcouru les boucles structurelles, les règles pilotées par les données (et compris pourquoi l’ordre d’itération des maps est important !), les fermetures, les fonctions d’ordre supérieur et, enfin, les goroutines et les canaux. C’est un excellent rappel du nombre de façons dont on peut résoudre un même problème en Go !


📚 Téléchargez Mon Ebook Gratuit 🔗

Obtenez l'édition hors ligne de Le Mode de Vie de Kaue—un guide pratique pour optimiser 🍏 Santé, 💰 Richesse, ❤️ Amour et 🧘 Sens.

Le Mode de Vie de Kaue

Le Mode de Vie de Kaue

📬 Abonnez-vous à Ma Newsletter 🔗

Recevez une notification lorsque je publie de nouveaux essais, résumés de livres et guides pratiques pour bien vivre. Sans spam—désabonnez-vous à tout moment.


💬 Commentaires et Discussion 🔗

Vous avez une question, une perspective ou une suggestion ? Partagez vos réflexions ci-dessous. Les commentaires fonctionnent avec GitHub Discussions (sans publicité et sans spam)—cliquez sur Se connecter avec GitHub ci-dessous pour commenter ou réagir (créer un compte GitHub gratuit prend moins d'une minute).



Mis à jour le 11 oct. 2026.

AVERTISSEMENT : Le contenu de ce site est fourni à titre purement informatif et éducatif et ne constitue pas un avis médical, psychologique, financier, d'investissement, fiscal ou juridique. Les idées, la sélection et les opinions présentées ici n'engagent que moi et ne représentent pas celles de mon employeur. Les résumés de livres, de conférences et d'articles sont des commentaires éducatifs indépendants et restent la propriété intellectuelle de leurs auteurs respectifs. Des outils d'IA peuvent être utilisés pour aider à l'édition, à la mise en forme et à la synthèse ; bien que tous les efforts soient déployés pour garantir l'exactitude, des erreurs peuvent survenir. CONFIDENTIALITÉ : Ce site utilise des services tiers pour l'abonnement facultatif à la newsletter (follow.it), les commentaires (giscus / GitHub Discussions) et les médias intégrés (YouTube), qui peuvent traiter les adresses IP et les cookies des visiteurs conformément à leurs politiques de confidentialité respectives.