Skip to Content
05 Best PracticesCode Propre

Code Propre

Principes et bonnes pratiques pour écrire du code maintenable, testable et évolutif en architecture hexagonale.

Un code propre est un code qui se lit comme de la prose et dont l’intention est immédiatement claire.


Principes SOLID

Les principes SOLID sont essentiels pour une architecture hexagonale réussie.

SOLID n’est pas une contrainte, c’est une boussole pour concevoir du code flexible et maintenable.


S - Single Responsibility Principle

Une classe = Une responsabilité = Une raison de changer

Violation SRP

// ❌ MAUVAIS : Trop de responsabilités @Controller public class TrendController { @Get public TrendResponseDto getTrends(String keyword, String region) { // 1. Validation if (keyword == null || keyword.length() < 2) { throw new IllegalArgumentException("Invalid keyword"); } // 2. Appel API externe String json = httpClient.get("https://api.example.com/trends?q=" + keyword); // 3. Parsing JSON JsonNode node = objectMapper.readTree(json); // 4. Conversion en DTO TrendDto dto = new TrendDto( node.get("keyword").asText(), node.get("value").asInt() ); // 5. Logique métier if (dto.getValue() > 80) { // Envoyer notification emailService.send(...); } return new TrendResponseDto(dto); } }

Problèmes :

  • ❌ Validation, API, parsing, métier, notification = 5 responsabilités
  • ❌ Impossible à tester unitairement
  • ❌ Modification d’une partie = risque de tout casser

O - Open/Closed Principle

Ouvert à l’extension, fermé à la modification

Violation OCP

// ❌ MAUVAIS : Modification de la classe pour chaque nouveau type @Singleton public class TrendRepository { public TrendResult getTrends(String keyword, String source) { if (source.equals("mock")) { return getMockData(keyword); } else if (source.equals("api")) { return getFromApi(keyword); } else if (source.equals("database")) { return getFromDatabase(keyword); } else if (source.equals("cache")) { // ← Modification ! return getFromCache(keyword); } throw new IllegalArgumentException("Unknown source"); } }

Problèmes :

  • ❌ Modification du code pour chaque nouveau type
  • ❌ Risque de régression
  • ❌ Violation du principe

L - Liskov Substitution Principle

Une classe fille doit pouvoir remplacer sa classe mère sans altérer le comportement

Violation LSP

// ❌ MAUVAIS : L'implémentation change le contrat public interface TrendRepository { Optional<TrendResult> getTrends(TrendQuery query); } @Singleton public class DatabaseTrendRepository implements TrendRepository { @Override public Optional<TrendResult> getTrends(TrendQuery query) { // ❌ Lance une exception au lieu de retourner Optional.empty() if (!databaseAvailable) { throw new RuntimeException("Database not available"); } return Optional.of(...); } }

Problèmes :

  • ❌ Le contrat dit Optional, l’implémentation lance une exception
  • ❌ Code client doit gérer des exceptions non prévues
  • ❌ Impossible de substituer sans casser le code

I - Interface Segregation Principle

Plusieurs interfaces spécifiques valent mieux qu’une interface générale

Violation ISP

// ❌ MAUVAIS : Interface trop large public interface TrendService { Optional<TrendResult> getTrends(TrendQuery query); void saveTrend(TrendResult result); void deleteTrend(Long id); List<TrendResult> searchTrends(String keyword); void exportToFile(String filename); void sendEmail(String to, TrendResult result); } // Client qui a besoin SEULEMENT de lire les trends @Singleton public class GetTrendsUseCase { private final TrendService service; // ❌ Force à dépendre de tout ! public TrendResult execute(TrendRequest request) { return service.getTrends(request.toQuery()) .orElseThrow(); } }

Problèmes :

  • GetTrendsUseCase dépend de méthodes qu’il n’utilise jamais
  • ❌ Changement dans sendEmail() = recompilation de GetTrendsUseCase
  • ❌ Interface difficile à implémenter (trop de méthodes)

D - Dependency Inversion Principle

Le principe fondamental de l’architecture hexagonale !

Dépendre des abstractions (interfaces), pas des implémentations concrètes

Violation DIP

// ❌ MAUVAIS : Dépendance directe à l'implémentation package org.smoka.application.usecase; import org.smoka.infrastructure.adapter.TrendRepositoryAdapter; // ❌ @Singleton public class GetTrendsUseCase { private final TrendRepositoryAdapter adapter; // ❌ Classe concrète public GetTrendsUseCase(TrendRepositoryAdapter adapter) { this.adapter = adapter; } }

Problèmes :

  • ❌ Impossible de changer d’implémentation
  • ❌ Couplage fort avec l’infrastructure
  • ❌ Tests difficiles (dépend d’une classe concrète)

Domain-Driven Design (DDD)

DDD structure le code autour du langage métier (Ubiquitous Language).

Ubiquitous Language

Le code doit utiliser le même vocabulaire que les experts métier.

Vocabulaire Technique

// ❌ MAUVAIS : Vocabulaire technique, pas métier public class DataProcessor { public Response processRequest(Map<String, Object> params) { String str1 = (String) params.get("param1"); String str2 = (String) params.get("param2"); // Appel API String jsonResult = apiClient.fetch(str1, str2); // Parsing Map<String, Object> data = parser.parse(jsonResult); return new Response(data); } }

Problèmes :

  • ❌ Impossible de comprendre le métier
  • param1, param2, data = aucun sens métier
  • ❌ Expert métier ne reconnaît pas son domaine

Value Objects

Les Value Objects représentent des concepts métier immuables et sans identité.

Obsession des Primitives

// ❌ MAUVAIS : Primitives partout public class User { private String email; // ❌ Pas de validation private String phone; // ❌ Pas de validation public User(String email, String phone) { this.email = email; // ❌ Email invalide possible this.phone = phone; // ❌ Phone invalide possible } } // Création avec données invalides var user = new User("invalid-email", "abc"); // ❌ Compile !

Problèmes :

  • ❌ Pas de validation
  • ❌ Duplication de la validation partout
  • ❌ Pas de règles métier encapsulées

Conventions de Nommage

Un bon nom explique l’intention sans nécessiter de commentaire.

Nommage des Classes

Domain Layer

TypeConventionExemples
EntitéNom métierUser, Order, Product
Value ObjectNom métierEmail, Money, Address
Port OutputVerbe + Repository/ServiceTrendRepository, NotificationService
ExceptionNom métier + ExceptionInvalidEmailException, OrderNotFoundException

Exemple complet :

// Entité public class Order { } // Value Object @Value public class OrderId { Long value; } // Port public interface OrderRepository { Optional<Order> findById(OrderId id); } // Exception public class OrderNotFoundException extends RuntimeException { }

Nommage des Méthodes

Une méthode bien nommée = auto-documentée.

Noms Vagues

// ❌ MAUVAIS : Noms vagues, pas clairs public class TrendService { public Data get(String s1, String s2) { } public void doStuff(Object o) { } public boolean check(Data d) { } public void process() { } }

Problèmes :

  • ❌ Impossible de comprendre sans lire le code
  • get, doStuff, check, process = trop vague
  • ❌ Nécessite des commentaires

Principes de Clean Code

Fonctions Courtes

Une fonction = Une chose = Bien la faire

Fonction Trop Longue

// ❌ MAUVAIS : Fonction de 50 lignes qui fait tout public TrendResponseDto getTrends(String keyword, String region) { // Validation (5 lignes) if (keyword == null) throw new IllegalArgumentException(); if (keyword.length() < 2) throw new IllegalArgumentException(); if (!keyword.matches("[a-zA-Z0-9 _-]+")) throw new IllegalArgumentException(); // Appel API (10 lignes) HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.example.com/trends")) .GET() .build(); HttpResponse<String> response = httpClient.send(request); // Parsing JSON (10 lignes) JsonNode root = objectMapper.readTree(response.body()); JsonNode trendsNode = root.get("trends"); List<TrendDto> trends = new ArrayList<>(); for (JsonNode node : trendsNode) { trends.add(new TrendDto( node.get("keyword").asText(), node.get("value").asInt() )); } // Logique métier (10 lignes) // ... encore 20 lignes return new TrendResponseDto(trends); }

Problèmes :

  • ❌ Impossible à tester unitairement
  • ❌ Difficile à comprendre
  • ❌ Trop de responsabilités

Éviter les Commentaires

Le code doit s’expliquer lui-même. Les commentaires mentent avec le temps.

Code avec Commentaires

// ❌ MAUVAIS : Commentaires partout public class TrendService { // Récupère les tendances pour un mot-clé et une région public TrendResult getTrends(String k, String r) { // Vérifier que le mot-clé n'est pas null if (k == null) { throw new IllegalArgumentException(); } // Appeler le repository Optional<TrendResult> result = repo.get(k, r); // Retourner le résultat ou lancer une exception return result.orElseThrow(); } }

Problèmes :

  • ❌ Commentaires compensent des noms vagues (k, r, repo.get)
  • ❌ Commentaires vont devenir obsolètes

Quand utiliser des commentaires ?

  • ✅ Expliquer le pourquoi (décisions architecturales)
  • ✅ Documentation Javadoc pour APIs publiques
  • Jamais pour expliquer le quoi (le code doit être clair)

Checklist Clean Code

Principes SOLID

  • SRP : Chaque classe a une seule responsabilité
  • OCP : Extension sans modification (interfaces + implémentations)
  • LSP : Les implémentations respectent le contrat
  • ISP : Interfaces petites et ciblées
  • DIP : Dépendre des abstractions, pas des implémentations

Domain-Driven Design

  • Vocabulaire métier utilisé partout (Ubiquitous Language)
  • Value Objects pour encapsuler les règles métier
  • Entités avec identité claire
  • Pas de dépendances techniques dans le domain

Conventions de Nommage

  • Classes : Noms métier explicites
  • Méthodes : Verbe + intention claire
  • Variables : Pas d’abréviations (request, pas req)
  • Packages : Organisation par couche (domain, application, infrastructure)

Clean Code

  • Fonctions courtes (3-10 lignes)
  • Pas de commentaires inutiles (code auto-documenté)
  • Pas de duplication (DRY - Don’t Repeat Yourself)
  • Tests pour chaque comportement

Récapitulatif

Un code propre = SOLID + DDD + Conventions claires + Fonctions courtes

Règles d’Or

  1. SRP : Une classe = Une responsabilité
  2. DIP : Dépendre des abstractions (architecture hexagonale !)
  3. Ubiquitous Language : Code = langage métier
  4. Value Objects : Encapsuler les règles de validation
  5. Fonctions courtes : 3-10 lignes maximum
  6. Code auto-documenté : Pas de commentaires inutiles

Prochaines Étapes

Vous maîtrisez maintenant les principes de code propre. Découvrez les optimisations !

Last updated on