Principes SOLID en Go : théorie et pratique de A à Z
SOLID est un acronyme désignant cinq principes fondamentaux de conception orientée objet, formulés par Robert Martin (Oncle Bob). Bien que Go ne soit pas un langage orienté objet classique (il n'a ni classes ni héritage au sens traditionnel), ces principes sont applicables et essentiels pour créer un code de qualité, maintenable et évolutif. Dans cet article, nous analyserons chaque principe SOLID avec des exemples issus de la pratique réelle en Go.
1. Single Responsibility Principle (SRP) — Principe de responsabilité unique
Essence : Chaque module, structure ou fonction ne doit avoir qu'une seule raison de changer. Autrement dit, ils doivent être responsables d'une tâche clairement définie.
Problème : Les développeurs placent souvent toute la logique dans une seule structure : validation, sauvegarde en base de données, envoi d'email, formatage de la réponse. Cela conduit à des « objets divins » (God Objects) difficiles à tester et à modifier.
Solution en Go : Séparez les responsabilités entre différentes structures et packages.
// ❌ Mauvais : Une seule structure fait touttype UserService struct { db *sql.DB mailer *Mailer}
func (s *UserService) CreateUser(name, email string) error { // 1. Validation if !strings.Contains(email, "@") { return errors.New("email invalide") } // 2. Sauvegarde en BDD _, err := s.db.Exec("INSERT INTO users (name, email) VALUES ($1, $2)", name, email) if err != nil { return err } // 3. Envoi d'un email de bienvenue return s.mailer.Send(email, "Bienvenue !", "Merci de vous être inscrit")}
// ✅ Bon : Séparation des responsabilitéstype UserValidator struct{}func (v *UserValidator) Validate(email string) error { if !strings.Contains(email, "@") { return errors.New("email invalide") } return nil}
type UserRepository struct { db *sql.DB}func (r *UserRepository) Save(name, email string) error { _, err := r.db.Exec("INSERT INTO users (name, email) VALUES ($1, $2)", name, email) return err}
type WelcomeMailer struct { mailer *Mailer}func (m *WelcomeMailer) Send(email string) error { return m.mailer.Send(email, "Bienvenue !", "Merci de vous être inscrit")}
type UserService struct { validator *UserValidator repo *UserRepository mailer *WelcomeMailer}
func (s *UserService) CreateUser(name, email string) error { if err := s.validator.Validate(email); err != nil { return err } if err := s.repo.Save(name, email); err != nil { return err } return s.mailer.Send(email)}Conseil pratique : Si vous ne pouvez pas décrire brièvement ce que fait votre fonction ou méthode sans utiliser la conjonction « et », alors le SRP est probablement violé.
2. Open-Closed Principle (OCP) — Principe d'ouverture/fermeture
Essence : Les entités logicielles (structures, fonctions, packages) doivent être ouvertes à l'extension, mais fermées à la modification. Cela signifie que vous devez pouvoir ajouter un nouveau comportement sans modifier le code existant.
Solution en Go : Utilisez des interfaces pour abstraire le comportement.
// ❌ Mauvais : Lors de l'ajout d'un nouveau type de paiement, il faut modifier la fonction ProcessPaymenttype PaymentProcessor struct{}
func (p *PaymentProcessor) Process(paymentType string, amount float64) { if paymentType == "credit_card" { // logique pour carte de crédit } else if paymentType == "paypal" { // logique pour PayPal } // Lors de l'ajout de "crypto" — il faut ajouter un nouveau else if}
// ✅ Bon : L'interface permet d'étendre les fonctionnalités sans modifier le codetype PaymentMethod interface { Pay(amount float64) error}
type CreditCard struct{}func