Aller au contenu

Semaine 5b : JOUR 1 – CLEAN CODE & PRINCIPES SOLID

Formation Java / Spring Boot – Qualité de code et professionnalisation


Objectifs

À l’issue de cette journée, vous serez capable de :


Pourquoi Clean Code ?

Le Clean Code n’est pas une religion. C’est une stratégie économique.

Dans un projet réel, la plupart du temps est passé à :

Un code pas clean coûte finalement plus cher que le temps gagné en le codant trop vite.


1) Clean Code : principes simples mais puissants

1.1 Le code est d’abord lu, ensuite écrit

Un.e développeur.euse lit beaucoup plus de code qu’il n’en écrit, sans compter l’utilisation de l’IA pour générer une partie du code.

Votre objectif : écrire du code qui se lit facilement comme une intention.


1.2 La règle d’or : une fonction doit faire une seule chose

Quand une méthode fait 3 choses :


1.3 Le vrai ennemi : la complexité cachée

Le code illisible crée :

Clean Code réduit la complexité en la rendant visible et structurée.


2) Règles concrètes de Clean Code

2.1 Les noms explicites

public void doIt(BigDecimal x) { ... }
public void crediter(BigDecimal montant) { ... }

But :


2.2 Variables courtes seulement si contexte court

BigDecimal s = compte.getSolde();
BigDecimal soldeActuel = compte.getSolde();

But : votre cerveau ne doit pas mémoriser des abréviations


2.3 Commentaires : quand et pourquoi et pas quoi

Un commentaire qui répète le code est inutile !

// on incrémente i
i++;
// Règle métier : les intérêts ne s'appliquent pas aux comptes clôturés

But :


2.4 Limiter la profondeur d’imbrication

Beaucoup trop de if imbriqués génère une logique illisible.

if (compte != null) {
  if (montant != null) {
    if (montant.compareTo(BigDecimal.ZERO) > 0) {
      ...
    }
  }
}
if (compte == null) throw new IllegalArgumentException("compte");
if (montant == null) throw new IllegalArgumentException("montant");
if (montant.compareTo(BigDecimal.ZERO) <= 0) throw new IllegalArgumentException("montant");
...

Ici, nous pourrions aussi utiliser un switch case.

But :


2.5 Éviter les effets de bord cachés

Une méthode qui modifie sans prévenir est dangereuse.

But :


3) Code Smells (à repérer rapidement)

On va en traiter plusieurs dans le TP…


PARTIE 2 – SOLID : COMPRENDRE ET APPLIQUER

SOLID = 5 principes simples, mais qui changent tout quand on fait du Spring.


4) S — Single Responsibility Principle (SRP)

Une classe doit avoir une seule raison de changer.

Un CompteService qui :

Trop de responsabilités !

But :


5) O — Open/Closed Principle (OCP)

Ouvert à l’extension, fermé à la modification.

Exemple : demain, un CompteJoint s’ajoute dans nos options de choix.

if (type.equals("COURANT")) ...
else if (type.equals("EPARGNE")) ...
else if (type.equals("JOINT")) ...

Mais vous avez déjà découvert l’intérêt des Patterns.

But :


6) L — Liskov Substitution Principle (LSP)

Un enfant doit pouvoir remplacer son parent sans casser le programme.

Exemple : si Compte doit implémenter la méthode debiter() alors un enfant (une sous-classe) ne doit pas trahir la règle

Mauvais : un CompteEpargne qui accepte un débit négatif ou qui fait n’importe quoi.

But :


7) I — Interface Ségrégation Principle (ISP)

Des petites interfaces ciblées plutôt qu’une grosse interface fourre-tout.

Mauvais :

interface CompteOperations {
  void debiter();
  void crediter();
  void calculerInterets();
  void exporterCsv();
}

Bon :

But :


8) D — Dependency Inversion Principle (DIP)

Il vaut mieux dépendre d’abstractions, plutôt que de classes concrètes.

Mauvais :

private final CompteRepositoryMemoire repo = new CompteRepositoryMemoire();

Bon :

private final CompteRepository repo;
public CompteService(CompteRepository repo) { this.repo = repo; }

But :


PARTIE 3 – ATELIER PRATIQUE : REFACTORISATION

9) TP GUIDÉ

Contexte

On vous donne un service avec trop de traitement :

@Service
public class BankService {

  public void operation(String type, String numeroSrc, String numeroDst, BigDecimal montant) {
    if(type.equals("VIREMENT")) {
      // trouver src, débiter, trouver dst, créditer, logs, validation...
    } else if(type.equals("CREDIT")) {
      // ...
    } else if(type.equals("DEBIT")) {
      // ...
    }
  }
}

Problèmes :


10) Travail demandé (en 5 étapes)

Étape 1 – Extraire les validations

Créer une classe dédiée : MontantValidator

Étape 2 – Extraire les cas d’usage

Créer :

Étape 3 – Utiliser des interfaces si besoin

Étape 4 – Supprimer les if sur type

Créer une stratégie (préfigure le pattern Strategy)

Étape 5 – Vérifier avec les tests

Les tests existants doivent passer sans modification fonctionnelle.


11) Corrigé (structure cible – simplifiée mais professionnelle)

Interface de stratégie

public interface OperationService {
    String type(); // "VIREMENT", "CREDIT", ...
    void executer(OperationRequest req);
}

Implémentation Virement

@Service
public class VirementService implements OperationService {

    private final CompteService compteService;

    public VirementService(CompteService compteService) {
        this.compteService = compteService;
    }

    @Override
    public String type() { return "VIREMENT"; }

    @Override
    public void executer(OperationRequest req) {
        compteService.virer(req.numeroSource(), req.numeroCible(), req.montant());
    }
}

Registre des stratégies

@Service
public class OperationDispatcher {

    private final Map<String, OperationService> operations;

    public OperationDispatcher(List<OperationService> services) {
        this.operations = services.stream()
            .collect(Collectors.toMap(OperationService::type, s -> s));
    }

    public void executer(OperationRequest req) {
        OperationService op = operations.get(req.type());
        if (op == null) throw new IllegalArgumentException("Type inconnu: " + req.type());
        op.executer(req);
    }
}

But :


12) Checklist Clean Code & SOLID

Avant de committer, vérifier :


Synthèse

Vous avez :


On abordera Git & travail collaboratif avec GitFlow sur GitLab