Aller au contenu principal

Gestion des erreurs et tests

Progression

#Gestion des erreurs, tests et observabilité

La robustesse d’une application Java ne naît pas d’outils isolés, mais d’un triptyque cohérent : une stratégie d’exception lisible, un socle de tests crédible et une observabilité qui raconte ce qui se produit en production. Ce chapitre ancre les concepts dans la pratique en construisant une petite application bancaire dont nous allons fiabiliser chaque couche.

#1. Concevoir une hiérarchie d’exceptions utile

Le langage distingue les exceptions contrôlées (checked) que le compilateur oblige à traiter, et les exceptions non contrôlées (RuntimeException) réservées aux erreurs programmatiques. Plutôt que d’enchaîner les throw new RuntimeException("..." ), définissez une hiérarchie qui reflète votre domaine :

javajava
1public sealed class BanqueException extends Exception permits2        CompteIntrouvableException,3        DecouvertInterditException,4        InfrastructureIndisponibleException {5    protected BanqueException(String message) {6        super(message);7    }8}9 10public final class DecouvertInterditException extends BanqueException {11    public DecouvertInterditException(BigDecimal solde, BigDecimal montant) {12        super("Découvert refusé: solde=" + solde + ", tentative=" + montant);13    }14}

Les exceptions contrôlées du domaine rendent explicite la liste des situations que le client doit gérer ; les problèmes de programmation (null inattendu, invariant cassé) restent des IllegalStateException. À chaque frontière (REST, CLI, batch) nous posons un handler qui transforme proprement l’exception en réponse HTTP, en message utilisateur ou en code de sortie.

#2. Construire une pyramide de tests durable

Une fois les invariants posés, les tests deviennent la première ligne de défense. Nous utilisons JUnit 5 pour structurer les scénarios et AssertJ pour clarifier les intentions.

javajava
1class CompteBancaireTest {2 3    @Test4    void debiter_refuse_le_decouvert() {5        var compte = new CompteBancaire("FR76…", new BigDecimal("100"));6 7        assertThatThrownBy(() -> compte.debiter(new BigDecimal("200")))8            .isInstanceOf(DecouvertInterditException.class)9            .hasMessageContaining("Découvert refusé");10    }11}

Les doubles de test restent utiles, mais privilégiez les bibliothèques qui favorisent la lisibilité : Mockito lorsque vous simulez des interactions, Testcontainers pour démarrer une base PostgreSQL éphémère et valider le mapping JPA ou jOOQ sur des données réalistes. Une fois la base de tests solide, poussez l’analyse avec Pitest (mutation testing) : l’outil modifie le bytecode pour vérifier que vos assertions détectent réellement les régressions.

#3. Observer ce qui se passe en production

Les exceptions correctement structurées et les tests exhaustifs ne suffisent pas ; il faut voir ce qui se passe lorsque l’application tourne. La combinaison SLF4J + Logback fournit une API uniforme et une configuration souple.

javajava
1private static final Logger LOGGER = LoggerFactory.getLogger(ServiceVirement.class);2 3public void effectuerVirement(Virement virement) {4    try {5        compteService.debiter(virement.source(), virement.montant());6        compteService.crediter(virement.cible(), virement.montant());7        LOGGER.info("Virement {} -> {} réussi", virement.source(), virement.cible());8    } catch (BanqueException e) {9        LOGGER.warn("Virement refusé", e);10        throw e;11    }12}

Ajoutez des métriques temps réel avec Micrometer :

javajava
1private final Timer virementTimer = Metrics.timer("banque.virement.latence");2 3public void traiter(Virement virement) throws BanqueException {4    virementTimer.record(() -> effectuerVirement(virement));5}

Les métriques sont exportées vers Prometheus, tandis que OpenTelemetry collecte les traces distribuées pour suivre une requête de bout en bout (REST → Kafka → worker). Ces éléments deviennent indispensables dès que vous devez diagnostiquer un ralentissement ou prouver une conformité réglementaire.

#4. Atelier guidé

  1. Enrichissez la hiérarchie d’exceptions pour distinguer les erreurs de validation (IBAN invalide) des indisponibilités d’infrastructure. Paramétrez @ControllerAdvice pour les convertir en réponses HTTP structuré.
  2. Écrivez un test d’intégration complet : démarrez votre base PostgreSQL via Testcontainers, insérez un client, déclenchez un virement et vérifiez les écritures en base.
  3. Ajoutez des métriques Timer et Counter autour du pipeline de virement, exposez-les sur /actuator/prometheus puis créez un tableau de bord Grafana. Simulez une saturation de la base pour observer les traces OpenTelemetry et les logs.

À la fin de ce chapitre, les étudiantes et étudiants disposent d’un socle cohérent pour fiabiliser leurs projets Java : ils savent comment structurer les exceptions, écrire des tests significatifs et installer l’observabilité nécessaire à une maintenance longue durée.