Écrire un code lisible et facile à maintenir en Java

Clean Code

Il n’est pas facile d’écrire du code propre, il doit être lisible et maintenable, pour soi-même et surtout pour les autres, qui devront peut-être un jour reprendre votre code pour le corriger, l’améliorer, ou je ne sais quels autres verbes en “-er”.

Dans cet article, nous verrons plusieurs sujets :

  • Le nommage (classes, méthodes & variables)
  • L’implémentation des méthodes
  • La gestion des exceptions
  • L’organisation des classes
  • L’écriture de commentaires

Bien nommer les classes, les méthodes et les variables

Le nommage est un aspect très important en développement informatique. Il ne faut pas sous-estimer l’importance d’une dénomination correcte dans le code. Les développeurs passent beaucoup de temps à lire du code et à en écrire.
Il n’est pas rare de se demander quel est le but d’une classe, quel type de données contient ce champ et quel est le rôle précis d’une méthode.

1 – Les classes

  • Le nom d’une classe doit être explicite. Lorsque vous créez une instance à partir d’une classe, elle doit être quelque chose qui existe physiquement. Il peut également s’agir d’une chose abstraite. Toujours essayer d’en faire un nom.
  • Le nom de la classe doit être spécifique. Si ce n’est pas le cas, nous devons à chaque fois aller à l’intérieur de la classe pour voir ce qu’elle contient. Par exemple, supposons que la classe CommonUtils.java contienne une méthode de génération de nombres aléatoires et une méthode de “split” de String. Il est préférable de faire de ces deux méthodes deux classes distinctes et de les nommer StringUtils.java et NumberUtils.java, par exemple.

2 – Les variables

  • Jamais une seule lettre
  • Toujours être précis sur l’utilité de la variable
  • Idéalement 1 ou 2 mots
  • Utiliser des préfixes pour les booléens (boolean) : “isActive”, “isValid”, “hasLicense”, ..
  • Utilisation camelCase : “myVariable”
  • Nommage des constantes en ALL_CAPS, tout en majuscule : CONSTANTE, THIRTEEN, ..

3 – Les méthodes

  • Une fonctionnalité doit être parfaitement compréhensible à partir de son nom
  • Le nom devrait être du type “verbe” + “nom“, mais il vaut mieux être spécifique, par exemple : setCustomerAge, calculatePrice, ..
  • Ne pas utiliser “if“, “or“, “and” dans les noms de méthodes



Implémentation des méthodes

Lors de l’implémentation d’une méthode, il convient d’examiner attentivement ce qu’il ne faut pas renvoyer, les paramètres de la méthode, les “conditionals“, l’application du principe de responsabilité unique (SRP).

Les méthodes peuvent retourner des primitives, des objets ou des “null”. Il est préférable d’éviter au maximum de retourner des “null“. Si l’appelant n’est pas au courant de l’existence de null, une NullPointerException peu se produire. Si l’appelant est au courant de null, il faut ajouter des vérifications supplémentaires. Cette condition supplémentaire ajoute de la complexité à chaque fois que la méthode est appelée, l’appelant doit ajouter des contrôles de “nullité“.

Une méthode ne doit faire qu’une seule chose. Le principe de responsabilité unique.

Éviter de renvoyer des codes spéciaux (-1, 0, 1, ..) depuis une méthode. Cela obligera le code client à vérifier les “nombres magiques“.

Toujours essayer d’avoir de 0 à 3 arguments dans une méthode. Plus d’arguments indiquera que la méthode a besoin d’être “remaniée“. Si la méthode prend trop de types primitifs, passer un seul objet.

Évitez les “flag” (boolean) comme paramètres de méthode. Les arguments “flag” compliquent la signature de la méthode. Par exemple, si nous avons une méthode sendNotification(text, isEmail), nous pouvons la diviser en deux méthodes sendSms(text) et sendEmail(text) :

public void sendNotification (Text text, boolean isEmail) {

            if (isEmail) {
                // Envoyer Email
            } else {
                // Envoyer SMS
            }
        }


Correction :

public void sensEmail (Text text) {
            // Code de la méthode
}

public void sendSms (Text text) {
            // Code de la méthode
}


Fail Fast” et “return early“. Signaler immédiatement tout échec et laissez le programme échouer. Si vous avez des calculs à l’intérieur d’une méthode, vérifiez en haut de la méthode certaines conditions afin d’éviter les exceptions et de lancer IllegalArgumentException. Nous pouvons donc lever l’exception tôt, avant qu’elle n’atteigne la partie logique :

private static double calculatePrice (double price) {

            if (price < 0) {
                throw new IllegalArgumentException("Message exception")
            }
            return getTotal() * price / 100;
}


Les instructions conditionnelles “if” multiples entraînent une complexité du code élevée. Il est très difficile de garder toutes ces conditions en tête en lisant le code. Pour éviter cela, nous pouvons d’abord vérifier les conditions simples et “return” immédiatement. Maintenant vous pouvez voir que les conditions et les déclarations de retour sont sur la même ligne. Si vous avez des valeurs d’arguments qui sont valides mais qui logiquement vous permettent d’arrêter l’exécution plus tôt, alors évaluez-les d’abord et quittez la fonction tout de suite :

public static String validationCode (Person person, int code) {

            if (isSystemUp) {
                if (person != null && person.getName().equals("")) {
                    if (person.getCode() != code) {
                        return "Code invalide";
                    }
                    return "Succès de la validation";
                }
                return "Nom invalide";
            }
            return "Erreur système";
}


Correction :

public static validationCode (Person person, int code) {
            if (!isSystemUp) return "Erreur système";
            if (person != null && person.getName().equals("")) return "Nom invalide";
            if (person.getCode() != code) return "Code invalide";
            return "Succès de la validation";
}


Ne pas se répéter. Si vous avez du code répétitif dans les méthodes, mettez-les dans une seule méthode.
Évitez les expressions ternaires imbriquées. Mettez-les plutôt dans des instructions if séparées pour un code plus lisible.




La gestion des exceptions

  • Ne pas “catchthrowable car possibilité d’attraper des erreurs, telles que des erreurs de mémoire ou des erreurs internes.
  • Essayez d’éviter de “catch” l’exception générale car vous risquez d’attraper des exceptions d’exécution qui ne devraient pas l’être, comme NullPointerException. Il faut plusieurs blocs “catch” pour résoudre ce problème. Ensuite, vous pouvez également afficher des messages d’exception spécifiques :
try {
     readFile();
     executeQuery();
} catch (Exception e) {
     // Traitement de l'exception
}


Mais :

try {
    readFile();
    executeQuery();
} catch (FileNotFoundException e) {
        // ........
} catch (IOException e) {
        // ........
} catch (SQLException e) {
        // ........
}


try {
     readFile();
     executeQuery();
} catch (IOException | SQLException e) {
     // Multi catch
}
  • Ne pas mettre de blocs de “catch” vides.
  • Dans le bloc “catch”, nous devons enregistrer l’erreur et lancer l’exception avec un message informatif.
  • Évitez les méthodes qui lèvent des exceptions dans le bloc “final“.



L’organisation des classes

Nous ne devons mettre que les méthodes qui sont spécifiques à la classe particulière. C’est ainsi qu’il faut appliquer le SRP aux classes.
Il faut mettre les choses ensemble pour qu’elles fonctionnent logiquement :

public class Order {

    String id;
    Date dateOrder;
    
    String getOrderId() {
        return this.id;
    }
    
    Date getDateOrder() {
        return this.dateOrder;
    }
    
    void sendOrderNotification() {
        // Code
    }
    
    void calculatePrice() {
        // Code
    }
}


Correction :

public class Order {

    String id;
    Date dateOrder;
    
    String getOrderId() {
        return this.id;
    }
    
    Date getDateOrder() {
        return this.dateOrder;
    }
    
    class NotificationService {
        void sendOrderNotification() {
            // Code
        }
    }
    
    class PriceCalculator {
        void calculatePrice() {
            // Code
        }
    }
}
  • Essayez toujours de programmer en fonction d’une interface. Si ce n’est pas le cas, vous devez passer par toutes les classes pour les modifier.
  • Mettez les méthodes “interdépendantes” les unes après les autres pour une meilleure lisibilité :
public class GoodOrder {
    void methodA() {
        methodB();
    }
    
    private void methodB() {
        methodC();
    }
    
    private void methodC() {
        methodD();
    }
    
    private void methodD() {
    }
}



L’écriture de commentaires

  • Ne mettez pas de commentaires redondants si une méthode ou une variable dit ce qu’elle fait.
  • Utilisez les commentaires JavaDoc si nécessaire.
  • Ne mettez pas de commentaires sur les getters et setters.
  • Les commentaires sur plusieurs lignes :
/*
Commentaire
en
plusieurs
lignes
*/


Des plugins de qualité de code pour IDE existent : SonarLint ou FindBugs par exemple.

1 réflexion sur “Écrire un code lisible et facile à maintenir en Java”

  1. Ping : Les principes SOLID – Programmation orientée objet

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Retour en haut
%d blogueurs aiment cette page :