Principe SRP en Software Craftsmanship, Comprendre et Appliquer le Single Responsibility Principle en Java

Par KamangaJan 16, 20247 mins de lecture

Principe SRP en Software Craftsmanship : Explications et Exemples en Java

Une classe qui fait tellement de choses qu'on ne sait plus par où l'attaquer pour la modifier : chaque équipe que j'accompagne en a au moins une. Ajouter une fonctionnalité ou corriger un bug devient un pari : on touche un endroit, on casse autre chose. Cette situation porte un nom : violation du principe SRP (Single Responsibility Principle). Robert C. Martin, qui l'a formalisé dans Clean Code, le résume ainsi : une classe ne devrait avoir qu'une seule raison de changer.

Le SRP est l'un des cinq principes SOLID, piliers du software craftsmanship. Comprendre le concept est trivial. L'appliquer sans tomber dans la sur-fragmentation l'est beaucoup moins. La plupart des équipes oscillent entre des classes monolithiques et un éclatement excessif où plus rien n'a de cohérence.

Cet article montre comment appliquer le SRP de manière pragmatique dans des projets Java, avec des exemples concrets tirés de cas réels.

Introduction au principe SRP

Le Single Responsibility Principle (SRP), ou principe de responsabilité unique, est souvent résumé ainsi : "Une classe ne devrait avoir qu'une seule raison de changer." Autrement dit, une classe doit se concentrer sur une seule tâche ou responsabilité.

L'idée derrière le SRP est simple, mais puissante. Lorsque vous suivez ce principe, chaque classe dans votre code est responsable d'un aspect spécifique du système, ce qui la rend plus facile à comprendre, à modifier et à tester. Si une classe a plusieurs responsabilités, elle devient complexe, et chaque modification pour une responsabilité peut avoir des impacts imprévus sur une autre.

Prenons un exemple simple :

public class UserManager {
    public void createUser(String username, String password) {
        // Logique pour créer un utilisateur
    }

    public void log(String message) {
        // Logique pour enregistrer un log
    }

    public void sendEmail(String email, String message) {
        // Logique pour envoyer un email
    }
}

Ici, la classe UserManager gère plusieurs responsabilités : la création d'utilisateur, la gestion des logs, et l'envoi d'emails. Si vous devez modifier la méthode createUser, vous risquez de casser des fonctionnalités de logging ou d'emailing, car tout est couplé dans une seule classe.

Pour appliquer le SRP, vous devez découper cette classe en plusieurs classes, chacune ayant une responsabilité bien définie :

public class UserManager {
    public void createUser(String username, String password) {
        // Logique pour créer un utilisateur
    }
}

public class Logger {
    public void log(String message) {
        // Logique pour enregistrer un log
    }
}

public class EmailService {
    public void sendEmail(String email, String message) {
        // Logique pour envoyer un email
    }
}

Maintenant, chaque classe a une seule responsabilité : UserManager s'occupe de la création d'utilisateur, Logger gère les logs, et EmailService est responsable de l'envoi des emails. Cela rend le code plus propre et plus facile à maintenir.

Vous voulez sentir où découper une classe sans tomber dans la sur-fragmentation ?

Identifier les responsabilités d'une classe, c'est un jugement qui se travaille, pas une règle qu'on récite. En mentoring 1:1, je relis votre code avec vous et on découpe vos vraies classes ensemble, jusqu'à ce que vous repériez les bonnes frontières d'instinct. Vous arrêtez d'osciller entre le monolithe et la poussière de mini-classes.

Pourquoi le SRP est-il important dans le Software Craftsmanship ?

Le software craftsmanship se concentre sur l’écriture d’un code de qualité, maintenable et évolutif. C’est un mouvement qui valorise les bonnes pratiques de développement et la création de logiciels avec soin et précision. Dans cette optique, le principe de responsabilité unique (SRP) joue un rôle clé.

Simplification et maintenabilité

Le SRP réduit la complexité du code. Une classe qui fait une seule chose se comprend et se modifie sans effort comparé à une classe qui en porte plusieurs : moins d'erreurs, plus de productivité d'équipe.

Facilite les tests unitaires

Le SRP rend aussi les tests unitaires plus simples. Une classe à responsabilité unique a généralement peu de dépendances, et chaque fonctionnalité se teste isolément sans avoir à simuler des comportements parasites.

Favorise l’évolutivité

Avec le SRP, étendre les fonctionnalités d'une application devient prévisible. Quand chaque classe a une responsabilité clairement définie, ajouter une fonctionnalité n'oblige pas à refactoriser une grande partie du code : on ajoute une nouvelle classe ou on modifie une classe existante sans risque de régression ailleurs. Dans une équipe que j'accompagnais dans l'assurance, les cycles de release sont passés de 6 semaines à 2 semaines après l'application du SRP sur les services métier les plus chargés.

3. Comment appliquer le SRP en Java : exemples concrets

Exemple 1 : Gestion des utilisateurs

Voici un exemple de violation du SRP avec une classe UserService qui gère à la fois la création d'utilisateurs et l'envoi d'emails.

public class UserService {
    public void createUser(String username, String password) {
        // Logique pour créer un utilisateur
        System.out.println("Utilisateur créé avec succès !");
        
        // Envoyer un email de bienvenue
        sendWelcomeEmail(username);
    }

    public void sendWelcomeEmail(String username) {
        // Logique pour envoyer un email de bienvenue
        System.out.println("Email envoyé à " + username);
    }
}

Cette classe gère deux responsabilités : la création d'utilisateur et l'envoi d'emails. En suivant le SRP, nous devrions séparer ces responsabilités dans deux classes distinctes.

public class UserService {
    private EmailService emailService;

    public UserService(EmailService emailService) {
        this.emailService = emailService;
    }

    public void createUser(String username, String password) {
        // Logique pour créer un utilisateur
        System.out.println("Utilisateur créé avec succès !");
        
        // Utilisation de l'EmailService pour envoyer l'email
        emailService.sendWelcomeEmail(username);
    }
}

public class EmailService {
    public void sendWelcomeEmail(String username) {
        // Logique pour envoyer un email de bienvenue
        System.out.println("Email envoyé à " + username);
    }
}

Exemple 2 : Traitement des paiements

Prenons l’exemple d’une classe PaymentProcessor qui viole le SRP en gérant à la fois le traitement des paiements et l’envoi des reçus.

public class PaymentProcessor {
    public void processPayment(String creditCard, double amount) {
        // Logique pour traiter le paiement
        System.out.println("Paiement de " + amount + "€ traité avec la carte " + creditCard);

        // Envoyer un reçu par email
        sendReceipt(creditCard, amount);
    }

    private void sendReceipt(String creditCard, double amount) {
        // Logique pour envoyer un reçu
        System.out.println("Reçu envoyé pour un paiement de " + amount + "€.");
    }
}

Ici, la classe gère deux responsabilités. En appliquant le SRP, on peut diviser cela en deux classes distinctes.

public class PaymentProcessor {
    private ReceiptSender receiptSender;

    public PaymentProcessor(ReceiptSender receiptSender) {
        this.receiptSender = receiptSender;
    }

    public void processPayment(String creditCard, double amount) {
        // Logique pour traiter le paiement
        System.out.println("Paiement de " + amount + "€ traité avec la carte " + creditCard);

        // Utilisation de ReceiptSender pour envoyer le reçu
        receiptSender.sendReceipt(creditCard, amount);
    }
}

public class ReceiptSender {
    public void sendReceipt(String creditCard, double amount) {
        // Logique pour envoyer un reçu
        System.out.println("Reçu envoyé pour un paiement de " + amount + "€.");
    }
}

Les avantages pratiques du SRP

Appliquer le SRP apporte plusieurs avantages concrets :

  • Lisibilité accrue : chaque classe porte une seule tâche, la compréhension du code est immédiate.
  • Maintenance et évolutions : modifier une fonctionnalité n'impacte plus le reste du système.
  • Tests unitaires plus simples : responsabilité unique = peu de dépendances à mocker.
  • Couplage réduit : le découpage des responsabilités casse les dépendances entre composants.
  • Réutilisabilité accrue : les classes spécialisées trouvent leur place dans d'autres projets.

Quand et pourquoi le SRP peut poser des défis

Risque de sur-fragmentation

Un SRP appliqué de manière dogmatique éclate le code en trop de classes et rend la navigation pénible. Cherchez l'équilibre : un cluster cohérent vaut mieux qu'une poussière de mini-classes.

Complexité dans les petites applications

Sur un prototype ou un petit projet, suivre rigoureusement le SRP ralentit le développement. C'est parfois acceptable de ne pas l'appliquer strictement quand le code restera simple ou temporaire.

Difficulté à identifier les responsabilités

Les frontières entre responsabilités sont parfois floues. Posez-vous la question : "Pourquoi cette classe pourrait-elle changer ?" Si la réponse contient plusieurs axes, vous tenez probablement une violation du SRP.

Refactorisation dans les projets existants

Appliquer le SRP à un projet existant chargé de dette technique demande une refactorisation lourde. Procédez par paliers : chaque classe découpée est un gain isolé, n'essayez pas de tout corriger d'un coup.

Le SRP n'est qu'une pratique parmi 100 pour coder propre

Savoir découper une classe selon ses responsabilités, c'est une des pratiques craft que j'applique au quotidien. Le Craft Bundle réunit les 100 que j'utilise pour écrire un code propre et maintenable, du SRP au reste. Ce sont les pratiques que l'IA ne vous apprendra jamais, parce qu'elle ne les a jamais vues tenir un projet sur la durée.


FAQ : Questions fréquemment posées sur le SRP

1. Qu’est-ce que le principe de responsabilité unique (SRP) ?

Le SRP stipule qu’une classe ne doit avoir qu’une seule responsabilité, c’est-à-dire qu’elle ne devrait avoir qu’une seule raison de changer.

2. Pourquoi est-il important de suivre le SRP ?

Le SRP améliore la lisibilité, la maintenabilité et la testabilité du code, et réduit le couplage entre composants.

3. Comment savoir si une classe viole le SRP ?

Si une classe a plusieurs responsabilités ou plusieurs raisons de changer, elle viole probablement le SRP.

4. Le SRP n’augmente-t-il pas le nombre de classes et la complexité du projet ?

Le SRP augmente effectivement le nombre de classes, mais chaque classe devient triviale à comprendre, et le projet, plus simple à maintenir.

5. Dois-je toujours appliquer le SRP, même dans les petits projets ?

Dans de petits projets ou prototypes, il est acceptable de ne pas appliquer strictement le SRP dès le début, mais le principe devient crucial à mesure que le projet évolue.


Ressource gratuite : Votre équipe livre-t-elle aussi vite qu'elle le pourrait ?

30 questions, 5 dimensions, score sur 100. Mesurez la maturité engineering de votre équipe et identifiez les 3 chantiers qui débloquent immédiatement votre vélocité.


Ecris par Kamanga

Expert IT avec 25 ans d'expérience en développement logiciel, diplômé EPITECH et MBA. Spécialisé en software craftsmanship, gestion du changement, stratégie, direction des systèmes d'information, coaching et certifié en agilité.