Chapitre 17
17 - Exercices
> Mettez en pratique vos compétences Go avec 40 exercices progressifs couvrant l'ensemble du spectre du développement Go moderne.
Cours 17 : Exercices pratiques Go
300+ lignes de contenu pédagogique avec 40 exercices progressifs
Introduction
Ce chapitre propose 40 exercices pratiques couvrant l'ensemble des concepts du langage Go, de la syntaxe de base jusqu'aux patterns avancés de concurrence, en passant par le développement d'APIs, l'accès aux bases de données, le testing, et le déploiement. Chaque exercice est conçu pour renforcer une compétence spécifique et comprend des objectifs d'apprentissage clairs.
Module 1 : Go Fundamentals (Exercices 01-05)
Exercice 01 : FizzBuzz avec gestion d'erreur
Objectif : Maîtriser les bases du contrôle de flux en Go.
Énoncé :
Écrivez une fonction FizzBuzz(n int) (string, error) qui retourne :
"Fizz"si n est divisible par 3"Buzz"si n est divisible par 5"FizzBuzz"si n est divisible par 3 et 5- Le nombre en chaîne sinon
Gérez les cas d'erreur : n négatif, n nul, overflow.
Concepts clés :
switch/caseerrorinterfacefmt.Sprintfmath.MaxInt/math.MinInt
Contraintes :
- Utilisez
switchsans expression (switch true) - Retournez une
errordescriptive avecfmt.Errorf - Pas de dépendances externes
Exercice 02 : Palindrome Unicode
Objectif : Manipuler les chaînes Unicode en Go.
Énoncé :
Implémentez IsPalindrome(s string) bool qui vérifie si une chaîne est un palindrome en ignorant la casse et les caractères non-lettres. Gérez correctement l'Unicode.
Concepts clés :
unicodepackagestrings.Map/unicode.ToLower- Runes vs bytes
- Unicode normalization (optionnel)
Indices :
func IsPalindrome(s string) bool {
f := func(r rune) rune {
if !unicode.IsLetter(r) {
return -1
}
return unicode.ToLower(r)
}
cleaned := strings.Map(f, s)
// Comparer cleaned avec son inverse...
}
Exercice 03 : Shape Interface
Objectif : Comprendre le système d'interfaces Go.
Énoncé :
Définissez une interface Shape avec les méthodes Area() float64 et Perimeter() float64. Implémentez les types Circle, Rectangle, Triangle. Ajoutez une fonction TotalArea(shapes ...Shape) float64.
Concepts clés :
- Interface definition
- Method sets
- Type assertion
- Variadic functions
Extensions :
- Ajoutez
String() stringpour chaque forme - Implémentez
sort.Interfacepour trier par aire
Exercice 04 : JSON Processing
Objectif : Maîtriser l'encodage/décodage JSON.
Énoncé : À partir de la structure :
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Emails []string `json:"emails,omitempty"`
Address *Address `json:"address,omitempty"`
}
Implémentez :
ParsePerson(data []byte) (Person, error)avecjson.UnmarshalSerializePerson(p Person) ([]byte, error)avecjson.MarshalIndent- Un
json.Marshalerpersonnalisé qui chiffre l'âge
Concepts clés :
- Struct tags
json.Marshal/json.Unmarshal- Custom marshal/unmarshal
io.Reader/io.Writer
Exercice 05 : Custom Sorting
Objectif : Utiliser sort et les fermetures (closures).
Énoncé :
Soit la structure Product{Name string, Price float64, Rating float64}. Implémentez le tri par :
- Nom (ascendant)
- Prix (descendant)
- Rating (ascendant)
- Note composée (weighted score)
Utilisez sort.Slice avec des closures.
Concepts clés :
sort.Slice- Closures capturant des variables
sort.Sortavec interface personnalisée
Module 2 : Goroutines & Channels (Exercices 06-11)
Exercice 06 : Worker Pool
Objectif : Implémenter le pattern worker pool.
Énoncé :
Créez un WorkerPool qui :
- Accepte N workers via un channel de jobs
- Chaque worker traite un job et envoie le résultat
- Supporte l'arrêt via context.Context
- Gère le graceful shutdown
Signature :
type Job func(ctx context.Context) (Result, error)
type Result struct {
Value interface{}
Err error
}
func NewPool(ctx context.Context, workers int) *Pool
func (p *Pool) Submit(job Job)
func (p *Pool) Results() <-chan Result
func (p *Pool) Wait()
Concepts clés :
- Goroutines et channels
sync.WaitGroupcontext.Contextpour annulation- Buffered vs unbuffered channels
Exercice 07 : Pipeline
Objectif : Chaîner des étapes de traitement avec des channels.
Énoncé : Implémentez un pipeline à 3 étapes :
Generate(numbers ...int) <-chan int: émet des nombresMultiplyBy2(in <-chan int) <-chan int: multiplie par 2FilterEven(in <-chan int) <-chan int: filtre les pairs
Connectez les étapes et affichez le résultat.
Concepts clés :
- Channel direction (
<-chan,chan<-) - Range over channels
- Goroutine lifecycle
- Pipeline pattern
Exercice 08 : Rate Limiter
Objectif : Implémenter un token bucket rate limiter.
Énoncé :
Créez un RateLimiter qui limite à N opérations par seconde avec burst autorisé. Utilisez un time.Ticker pour remplir le bucket.
Signature :
func NewRateLimiter(rate int, burst int) *RateLimiter
func (rl *RateLimiter) Allow() bool
func (rl *RateLimiter) Wait(ctx context.Context) error
Concepts clés :
time.Tickertime.NewTicker- Token bucket algorithm
- Select with default
Exercice 09 : Timeout avec Context
Objectif : Maîtriser context.Context pour les timeouts et l'annulation.
Énoncé :
Écrivez une fonction FetchWithTimeout(ctx context.Context, url string, timeout time.Duration) (string, error) qui simule un appel HTTP long. Si le timeout expire, annulez et retournez une erreur.
Concepts clés :
context.WithTimeoutcontext.WithCancelselectavec<-ctx.Done()- Propagation de l'annulation
Exercice 10 : Merge Channels
Objectif : Fusionner dynamiquement N channels.
Énoncé :
Implémentez Merge(channels ...<-chan int) <-chan int qui fusionne N channels en un seul. Tous les messages de tous les channels d'entrée doivent être forwarded vers le channel de sortie.
Deux approches sont attendues :
- Avec
sync.WaitGroupet goroutines séparées - Avec un pattern de
selectetreflect.Select
Concepts clés :
- Fan-in pattern
sync.WaitGroupreflect.Selectpour le cas dynamique- Gestion de fermeture des channels
Exercice 11 : Prime Sieve (Crible d'Ératosthène)
Objectif : Implémenter un algorithme classique en concurrent.
Énoncé : Implémentez le crible d'Ératosthène concurrent de Go :
func Generate(ch chan<- int)
func Filter(in <-chan int, out chan<- int, prime int)
func Sieve(limit int) []int
Chaque nombre premier lance un nouveau filtre (goroutine).
Concepts clés :
- Infinite channel communication
- Dynamic goroutine creation
- Prime sieve pattern (Rob Pike)
Module 3 : Concurrency Patterns (Exercices 12-15)
Exercice 12 : Pub/Sub
Objectif : Implémenter un bus publish-subscribe thread-safe.
Énoncé :
Créez un PubSub générique qui supporte :
Subscribe(topic string) <-chan interface{}Publish(topic string, msg interface{})Unsubscribe(topic string, ch <-chan interface{})Close()(ferme tous les channels proprement)
Concepts clés :
sync.RWMutexpour l'accès concurrent- Map de topic -> []chan
- Éviter les fuites de goroutines
- Broadcasting via range sur les subscribers
Exercice 13 : Circuit Breaker
Objectif : Pattern de résilience avec circuit breaker.
Énoncé :
Implémentez un circuit breaker avec 3 états : Closed, Open, HalfOpen.
- Closed : les appels passent, après N échecs -> Open
- Open : les appels échouent immédiatement, après timeout -> HalfOpen
- HalfOpen : un appel test, si succès -> Closed, si échec -> Open
Signature :
type CircuitBreaker struct {
// ...
}
func NewCircuitBreaker(maxFailures int, resetTimeout time.Duration) *CircuitBreaker
func (cb *CircuitBreaker) Execute(fn func() error) error
Concepts clés :
- State machine
sync.Mutexpour la protection- Atomic operations
- Pattern de résilience
Exercice 14 : Fan-Out/Fan-In
Objectif : Paralléliser le traitement avec agrégation.
Énoncé : Implémentez un système de traitement de mots :
FanOut(in <-chan string, workers int) []<-chan string: distribue le travailFanIn(channels ...<-chan string) <-chan string: agrège les résultats- Les workers comptent les lettres de chaque mot
Concepts clés :
- Parallélisation déterministe
- Distribution équitable (round-robin)
- Agrégation correcte des résultats
- Gestion des goroutines zombies
Exercice 15 : Pipeline with Error Handling
Objectif : Pipeline avec gestion d'erreurs.
Énoncé : Étendez le pipeline de l'exercice 07 avec :
- Un channel d'erreurs séparé
- Un
errorgroup (golang.org/x/sync/errgroup) - Un mécanisme de retry (3 tentatives max)
- Un reporting des erreurs à la fin
Concepts clés :
errgroup.Group- Error channel pattern
- Retry with backoff
- Graceful degradation
Module 4 : HTTP API (Exercices 16-19)
Exercice 16 : REST API CRUD
Objectif : Construire une API REST complète.
Énoncé :
Créez une API REST pour la ressource Book :
GET /api/books: liste paginéeGET /api/books/:id: détailPOST /api/books: créationPUT /api/books/:id: mise à jourDELETE /api/books/:id: suppression
Stockage en mémoire avec sync.RWMutex. Validation des entrées.
Concepts clés :
net/httpstandardgorilla/muxouchirouter- JSON encoding/decoding
- Pagination, validation
Exercice 17 : Middleware Chain
Objectif : Comprendre et implémenter des middlewares.
Énoncé : Implémentez une chaîne de middlewares :
LoggingMiddleware: log méthode, path, duréeAuthMiddleware: vérifie un token JWT simple (HMAC)RateLimitMiddleware: limite par IP (token bucket)RecoveryMiddleware: récupération des panicsCORSMiddleware: en-têtes CORS
Le tout doit être chainable : r.Use(m1, m2, m3, m4, m5)
Concepts clés :
func(http.Handler) http.Handlerpatternhttp.HandlerinterfaceresponseWriterwrapper pour capturer le status- Composition de middlewares
Exercice 18 : Graceful Shutdown
Objectif : Arrêt gracieux d'un serveur HTTP.
Énoncé : Implémentez un serveur HTTP avec :
- Signal handling (
SIGINT,SIGTERM) shutdownavec timeout (30s max)- Drain des connexions actives
- Context propagation aux handlers longs
Concepts clés :
os/signalpackagehttp.Server.Shutdown()context.WithTimeout- Connection draining
Exercice 19 : REST avec chi et validation
Objectif : API REST professionnelle avec chi.
Énoncé :
Utilisez chi (ou gorilla/mux) pour créer une API avec :
go-playground/validatorpour la validationhttpinpour le parsing des query paramsslogpour le logging structuréchi.middlewarebuilt-ins (Logger, Recoverer, RealIP, RequestID, Timeout)
Concepts clés :
chirouter- Struct validation with tags
- Structured logging
- Request/response decoders
Module 5 : Database (Exercices 20-23)
Exercice 20 : PostgreSQL CRUD
Objectif : Opérations CRUD avec PostgreSQL et pgx.
Énoncé :
Avec pgx (ou database/sql + lib/pq), implémentez :
type UserRepository interface {
Create(ctx context.Context, user *User) error
GetByID(ctx context.Context, id uuid.UUID) (*User, error)
List(ctx context.Context, limit, offset int) ([]User, error)
Update(ctx context.Context, user *User) error
Delete(ctx context.Context, id uuid.UUID) error
}
Avec des migrations SQL (golang-migrate).
Concepts clés :
pgxoudatabase/sql- SQL migrations
uuidgeneration- Prepared statements
Exercice 21 : Redis Caching
Objectif : Cache Redis avec patterns avancés.
Énoncé : Implémentez un cache Redis avec :
- Set/Get avec expiration TTL
- Pattern Cache-Aside
- Pattern de cache invalidation
- Rate limiting (Redis)
- Distributed locks avec
SETNX
Concepts clés :
go-redis/redisclientSET,GET,EXPIRESETNXpour les locks- Pipeline Redis pour batch
Exercice 22 : Repository Pattern
Objectif : Implémenter le pattern repository avec des tests.
Énoncé :
Créez une interface UserRepository avec deux implémentations :
postgresRepository(PostgreSQL)inMemoryRepository(utilisée pour les tests)
Le but est de tester la couche service sans base de données.
Concepts clés :
- Interface-based repository
- Dependency injection
- In-memory test implementation
go-sqlmockpour les tests SQL
Exercice 23 : Transaction Management
Objectif : Transactions PostgreSQL complexes.
Énoncé : Implémentez un transfert bancaire entre deux comptes :
- Débiter le compte A
- Créditer le compte B
- Enregistrer la transaction dans un journal
- Rollback si une étape échoue
Avec isolation niveau SERIALIZABLE.
Concepts clés :
BEGIN/COMMIT/ROLLBACK- Transaction isolation levels
pgx.Tx/database/sql.Tx- Deadlock detection
Module 6 : Testing (Exercices 24-27)
Exercice 24 : Table-Driven Tests
Objectif : Tests paramétrés en Go.
Énoncé :
Testez la fonction CalculateTax(amount float64, rate float64) (float64, error) avec des table-driven tests couvrant :
- Cas normaux (taux 0%, 5.5%, 20%)
- Cas limites (0, montant négatif, taux négatif)
- Cas de précision flottante
- Sous-tests avec
t.Run()
Concepts clés :
testing.T- Table-driven tests
t.Runpour sous-testst.Parallel
Exercice 25 : Mocking avec testify
Objectif : Mocking d'interfaces.
Énoncé :
Avec testify/mock, mockez l'interface EmailSender :
type EmailSender interface {
Send(to, subject, body string) error
}
Testez un NotificationService qui utilise EmailSender sans envoyer de vrais emails.
Concepts clés :
testify/mock- Interface mocking
testify/assert/testify/requiregomock(alternative)
Exercice 26 : Integration Tests
Objectif : Tests d'intégration avec testcontainers.
Énoncé :
Avec testcontainers-go, créez des tests d'intégration qui :
- Lancen un container PostgreSQL
- Exécutent les migrations
- Insèrent des données de test
- Testent les opérations CRUD
- Nettoient le container après le test
Concepts clés :
testcontainers-gotesting.Mpour setup/teardown global- Build tags (
//go:build integration) - Docker lifecycle management
Exercice 27 : Fuzzing
Objectif : Tests de fuzzing.
Énoncé :
Écrivez un test de fuzzing pour la fonction ParsePhoneNumber(s string) (PhoneNumber, error) qui parse un numéro de téléphone au format international.
Le fuzzer doit découvrir des cas cassant le parsing.
Concepts clés :
testing.F/f.Fuzz- Corpus de seed
go test -fuzz- Edge cases in string parsing
Module 7 : CLI avec Cobra (Exercices 28-30)
Exercice 28 : CLI Todo List
Objectif : Première application CLI.
Énoncé : Créez une todo-list CLI avec :
todo add "task description"todo list [--all] [--done]todo done <id>todo delete <id>- Stockage JSON dans
~/.todo/tasks.json
Concepts clés :
cobra.Command- Persistent flags
- JSON file storage
os/userpour le chemin home
Exercice 29 : CLI with Config
Objectif : CLI lisant une configuration.
Énoncé : Créez une CLI qui lit sa configuration depuis :
- Flags CLI
- Fichier YAML (
~/.app/config.yaml) - Variables d'environnement (préfixe
APP_) Avec viper pour la gestion de configuration.
Concepts clés :
viperlibrary- Configuration precedence
- YAML parsing
- Env variable binding
Exercice 30 : Multi-Command CLI
Objectif : CLI complexe avec sous-commandes.
Énoncé : Créez un CLI de gestion de projet avec :
project init <name>: initialise un projetproject build [--output] [--tags]: build le projetproject deploy --env <env>: déploie le projetproject version: affiche la version- Hooks pre/post exécution
Concepts clés :
- Command nesting
- PersistentRun / PreRun
- Viper config binding
- Cobra CLI best practices
Module 8 : gRPC (Exercices 31-33)
Exercice 31 : Unary gRPC
Objectif : Service gRPC unary.
Énoncé :
Définissez un service UserService en protobuf :
service UserService {
rpc CreateUser (CreateUserRequest) returns (User);
rpc GetUser (GetUserRequest) returns (User);
rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
}
Implémentez le serveur et le client.
Concepts clés :
- Protobuf definition
protocgeneration- gRPC server setup
- gRPC client
Exercice 32 : Server Streaming
Objectif : Streaming côté serveur.
Énoncé :
Créez un service LogService avec un endpoint :
rpc StreamLogs(StreamLogsRequest) returns (stream LogEntry);
Le serveur stream les logs en temps réel. Le client les affiche au fur et à mesure.
Concepts clés :
- Server-side streaming
grpc.ServerStream- Context cancellation
- Backpressure
Exercice 33 : Bidirectional Streaming
Objectif : Streaming bidirectionnel (chat).
Énoncé : Implémentez un chat via gRPC streaming bidirectionnel :
service ChatService {
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
Chaque client envoie et reçoit des messages simultanément.
Concepts clés :
- Bidirectional streaming
grpc.ClientStream- Goroutine management
- Broadcast pattern
Module 9 : Generics (Exercices 34-36)
Exercice 34 : Generic Stack
Objectif : Pile générique thread-safe.
Énoncé : Implémentez une pile générique thread-safe :
type Stack[T any] struct { ... }
func New[T any]() *Stack[T]
func (s *Stack[T]) Push(item T)
func (s *Stack[T]) Pop() (T, bool)
func (s *Stack[T]) Peek() (T, bool)
func (s *Stack[T]) Len() int
func (s *Stack[T]) IsEmpty() bool
Concepts clés :
- Type parameters
sync.RWMutex- Zero value of T
- Generic constraints
Exercice 35 : Generic Map/Reduce/Filter
Objectif : Higher-order functions génériques.
Énoncé : Implémentez :
func Map[T, U any](s []T, f func(T) U) []U
func Filter[T any](s []T, f func(T) bool) []T
func Reduce[T, U any](s []T, init U, f func(U, T) U) U
Avec des tests pour différents types (int, string, struct).
Concepts clés :
- Multiple type parameters
- Generic functions
- Functional programming in Go
- Type inference
Exercice 36 : Generic Cache
Objectif : Cache typé avec expiration.
Énoncé : Implémentez un cache générique avec TTL :
type Cache[K comparable, V any] struct { ... }
func NewCache[K comparable, V any](ttl time.Duration, cleanupInterval time.Duration) *Cache[K, V]
func (c *Cache[K, V]) Get(key K) (V, bool)
func (c *Cache[K, V]) Set(key K, value V)
func (c *Cache[K, V]) Delete(key K)
func (c *Cache[K, V]) Len() int
func (c *Cache[K, V]) Clear()
Concepts clés :
comparableconstraint- Generic + concurrency
- TTL with background cleanup
- Memory management
Module 10 : Profiling (Exercices 37-38)
Exercice 37 : CPU/Memory Profiling
Objectif : Profiler et optimiser une application.
Énoncé :
- Créez une application qui alloue massivement (JSON parsing, string concatenation)
- Profilez le CPU :
go test -cpuprofile cpu.prof -bench . - Profilez la mémoire :
go test -memprofile mem.prof -bench . - Analysez avec
go tool pprof - Optimisez les hotspots identifiés
Concepts clés :
pprofpackagego tool pprof(text, graph, flamegraph)- Benchmark with profiling
- Escape analysis
- Memory allocation optimization
Exercice 38 : Trace Execution
Objectif : Tracer l'exécution concurrente.
Énoncé :
- Créez une application concurrente (worker pool ou pipeline)
- Générez une trace :
go test -trace trace.out - Analysez avec
go tool trace - Identifiez les goulets d'étranglement, les goroutines bloquées, les problèmes de scheduling
Concepts clés :
runtime/tracepackagego tool trace- Goroutine scheduling
- Blocking profiles
- Network contention
Module 11 : Docker / Kubernetes (Exercices 39-40)
Exercice 39 : Multi-stage Docker Build
Objectif : Dockerfile multi-stage optimisé.
Énoncé : Créez un Dockerfile multi-stage pour une application Go :
- Stage 1 : Build avec
golang:1.23-alpine, cache des dépendances, build statique - Stage 2 : Image
scratchminimaliste - Stage 3 (optionnel) : Image
distrolesspour debugging
Optimisations : CGO_ENABLED=0, -ldflags="-s -w", UPX compression.
Concepts clés :
- Multi-stage builds
scratch/distrolessimages- Build args
- Layer caching optimization
- Security (non-root user)
Exercice 40 : Deploy on Kubernetes
Objectif : Déploiement Kubernetes complet.
Énoncé : Créez les manifests Kubernetes pour l'API de l'exercice 16 :
- Deployment (3 replicas, rolling update)
- Service (ClusterIP)
- ConfigMap (configuration)
- HorizontalPodAutoscaler (CPU > 70%)
- Readiness + Liveness probes
- Resource limits
Concepts clés :
- Kubernetes manifests YAML
kubectl apply- Rolling updates
- Probes (readiness, liveness)
- HPA (Horizontal Pod Autoscaler)
Conseils généraux
- Lisez la spec attentivement avant de coder
- Écrivez les tests en premier (TDD) quand c'est approprié
- Utilisez
go vetetgolangci-lintaprès chaque exercice - Ne regardez PAS le corrigé avant d'avoir terminé
- Comparez votre solution avec le corrigé pour apprendre
- Exécutez les benchmarks pour les exercices de performance