Craftmanship · Fiche 02

Bonnes Pratiques

Règles fondamentales pour un code propre : nommage, KISS, DRY, exceptions, formatage et chasse aux magic numbers.

Règles fondamentales pour un code propre.

Conventions de Nommage & Noms Significatifs

// Noms courts et peu descriptifs
public class Usr {
    private int id;
    private String n;
    private String e;
    private boolean a;

    public boolean chk() {
        return a && e != null;
    }

    public void proc() {
        // Traitement
    }
}
// Conventions Java respectées, noms explicites
public class User {
    private int userId;
    private String name;
    private String email;
    private boolean active;

    // Méthodes avec verbes
    public boolean isValid() {
        return active && email != null;
    }

    // CamelCase pour les méthodes
    public void processUserRegistration() {
        // Traitement
    }
}

Conventions Java :

  • Classes : PascalCase (UserService)
  • Méthodes/variables : camelCase (getUserById)
  • Constantes : SNAKE_CASE_MAJUSCULE (MAX_RETRY_COUNT)
  • Packages : minuscules (com.example.project)

Principes KISS & DRY

KISS (Keep It Simple, Stupid)

Privilégier la simplicité. Si une solution simple existe, l’utiliser.

Trop complexe : une suroptimisation prématurée qui sacrifie la lisibilité.

// Suroptimisation prématurée
public boolean isWeekend(Date date) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);

    // Utilisation de bitwise operations pour "optimiser"
    int weekendBits = (1 << Calendar.SATURDAY) |
                      (1 << Calendar.SUNDAY);

    return ((1 << dayOfWeek) & weekendBits) != 0;
}

Simple et clair : la même logique, immédiatement compréhensible.

// Solution simple et lisible
public boolean isWeekend(Date date) {
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);

    return dayOfWeek == Calendar.SATURDAY ||
           dayOfWeek == Calendar.SUNDAY;
}

DRY (Don’t Repeat Yourself)

Éviter la duplication de code. Extraire les parties communes.

// Duplication de la logique de validation
public class UserService {
    public void createUser(User user) {
        // Validation email
        if (user.getEmail() == null ||
            !user.getEmail().matches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$")) {
            throw new IllegalArgumentException("Email invalide");
        }
        // Sauvegarde...
    }

    public void updateUser(User user) {
        // Même validation dupliquée
        if (user.getEmail() == null ||
            !user.getEmail().matches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$")) {
            throw new IllegalArgumentException("Email invalide");
        }
        // Mise à jour...
    }
}
// Extraction de la logique commune
public class UserService {
    public void createUser(User user) {
        validateEmail(user.getEmail());
        // Sauvegarde...
    }

    public void updateUser(User user) {
        validateEmail(user.getEmail());
        // Mise à jour...
    }

    // Méthode réutilisable
    private void validateEmail(String email) {
        if (email == null ||
            !email.matches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$")) {
            throw new IllegalArgumentException("Email invalide");
        }
    }
}

Traitement des Exceptions

// Mauvais traitement des exceptions
public void processFile(String path) {
    try {
        // Ouvre un fichier
        FileInputStream file = new FileInputStream(path);
        // Traitement...

        // Exceptions génériques
    } catch (Exception e) {
        // Masque l'erreur réelle
        e.printStackTrace();

        // Ou pire, ignore l'exception
    } finally {
        // Oubli de fermer les ressources
    }
}
// Traitement correct des exceptions
public void processFile(String path) {
    // try-with-resources pour fermeture automatique
    try (FileInputStream file = new FileInputStream(path)) {
        // Traitement...

    // Exceptions spécifiques d'abord
    } catch (FileNotFoundException e) {
        // Journalisation structurée
        logger.error("Fichier non trouvé: {}", path, e);
        throw new ServiceException("Impossible de traiter le fichier", e);

    } catch (IOException e) {
        logger.error("Erreur lors de la lecture du fichier: {}", path, e);
        throw new ServiceException("Erreur de lecture", e);
    }
}

Bonnes pratiques pour les exceptions :

  • Utiliser try-with-resources pour les ressources à fermer
  • Attraper les exceptions spécifiques avant les génériques
  • Journaliser avec contexte (paramètres, état)
  • Encapsuler les exceptions techniques en exceptions métier
  • Ne jamais ignorer une exception (catch vide)

Piège d’entretien : un catch (Exception e) {} vide « avale » l’erreur : le programme continue dans un état incohérent et le bug devient introuvable en production.

Formatage Cohérent du Code

// Style incohérent
public class UserService{
  private UserRepository userRepo;
public UserService(UserRepository repo) {
    this.userRepo=repo;}

    public User findById(Long id){
        if(id==null)
        return null;

        return userRepo.findById(id).orElse(null);
    }

  public List<User> findAll()
  { return userRepo.findAll(); }
}
// Style cohérent
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User findById(Long id) {
        if (id == null) {
            return null;
        }

        return userRepository.findById(id).orElse(null);
    }

    public List<User> findAll() {
        return userRepository.findAll();
    }
}

Conseils pour un formatage cohérent :

  • Utiliser un outil de formatage automatique (ex. : Google Java Format)
  • Configurer des hooks pre-commit pour vérifier le formatage
  • Définir un style d’équipe dans un fichier .editorconfig
  • Appliquer les mêmes règles d’indentation partout
  • Limiter la longueur des lignes (généralement 80-120 caractères)

Éviter la Duplication de Code

// Duplication entre classes
public class CustomerValidator {
    public boolean isValidEmail(String email) {
        return email != null &&
               email.matches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$");
    }
}

public class EmployeeValidator {
    // Même logique dupliquée
    public boolean isValidEmail(String email) {
        return email != null &&
               email.matches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$");
    }
}

public class SupplierValidator {
    // Encore la même logique
    public boolean validateEmail(String email) {
        if (email == null) return false;
        return email.matches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$");
    }
}
// Extraction dans une classe utilitaire
public class ValidationUtils {
    // Méthode statique réutilisable
    public static boolean isValidEmail(String email) {
        return email != null &&
               email.matches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$");
    }
}

// Utilisation dans les différentes classes
public class CustomerValidator {
    public boolean validateCustomer(Customer customer) {
        return ValidationUtils.isValidEmail(customer.getEmail());
    }
}

public class EmployeeValidator {
    public boolean validateEmployee(Employee employee) {
        return ValidationUtils.isValidEmail(employee.getEmail());
    }
}

Techniques pour éviter la duplication :

  • Extraire le code commun dans des méthodes utilitaires
  • Utiliser l’héritage pour partager des comportements
  • Appliquer des design patterns comme Template Method ou Strategy
  • Créer des bibliothèques internes pour les fonctionnalités communes
  • Utiliser des outils d’analyse comme SonarQube pour détecter la duplication

Éviter les Magic Numbers

Un magic number est une valeur constante utilisée dans le code sans explication claire de sa signification.

// Valeurs mystérieuses sans contexte
public class UserService {
    public boolean canAccess(User user) {
        // Pourquoi 18 ? Pourquoi 65 ?
        return user.getAge() > 18 && user.getAge() < 65;
    }

    public void processPayment(BigDecimal amount) {
        // Que représente 0.20 ?
        BigDecimal tax = amount.multiply(BigDecimal.valueOf(0.20));

        // Et 100 ?
        if (amount.compareTo(BigDecimal.valueOf(100)) > 0) {
            // Logique de réduction
        }
    }

    public void scheduleTask() {
        // 86400000 millisecondes = ?
        timer.schedule(task, 86400000);
    }
}
// Constantes explicites et documentées
public class UserService {
    private static final int MINIMUM_AGE = 18;
    private static final int MAXIMUM_AGE = 65;
    private static final BigDecimal TAX_RATE = BigDecimal.valueOf(0.20);
    private static final BigDecimal FREE_SHIPPING_THRESHOLD = BigDecimal.valueOf(100);
    private static final long ONE_DAY_IN_MILLIS = 24 * 60 * 60 * 1000;

    public boolean canAccess(User user) {
        return user.getAge() > MINIMUM_AGE &&
               user.getAge() < MAXIMUM_AGE;
    }

    public void processPayment(BigDecimal amount) {
        BigDecimal tax = amount.multiply(TAX_RATE);

        if (amount.compareTo(FREE_SHIPPING_THRESHOLD) > 0) {
            // Livraison gratuite
        }
    }

    public void scheduleTask() {
        timer.schedule(task, ONE_DAY_IN_MILLIS);
    }
}

Avantages des constantes nommées :

  • Code auto-documenté : la signification est claire
  • Maintenance facilitée : changement en un seul endroit
  • Réduction des erreurs : pas de recopie de valeurs
  • Meilleure lisibilité et compréhension du code
  • Possibilité d’ajouter des commentaires explicatifs

Interro écrite

8 questions pour valider la fiche. Une seule réponse par question.

Question 1/8Quelle est la convention Java pour nommer une constante ?