JavaRush /Blog Java /Random-FR /Pause café #56. Un guide rapide des meilleures pratiques ...

Pause café #56. Un guide rapide des meilleures pratiques en Java

Publié dans le groupe Random-FR
Source : DZone Ce guide comprend les meilleures pratiques et références Java pour améliorer la lisibilité et la fiabilité de votre code. Les développeurs ont la grande responsabilité de prendre les bonnes décisions chaque jour, et la meilleure chose qui peut les aider à prendre les bonnes décisions est l'expérience. Et même si tous n’ont pas une vaste expérience dans le développement de logiciels, chacun peut profiter de l’expérience des autres. J'ai préparé pour vous quelques recommandations que j'ai tirées de mon expérience avec Java. J'espère qu'ils vous aideront à améliorer la lisibilité et la fiabilité de votre code Java.Pause café #56.  Un guide rapide des meilleures pratiques en Java - 1

Principes de programmation

N'écrivez pas de code qui fonctionne . Efforcez-vous d'écrire du code maintenable , pas seulement par vous, mais par toute autre personne susceptible de travailler sur le logiciel à l'avenir. Un développeur passe 80 % de son temps à lire du code et 20 % à écrire et tester du code. Alors, concentrez-vous sur l’écriture de code lisible. Votre code ne devrait avoir besoin de commentaires pour que quiconque comprenne ce qu'il fait. Pour écrire du bon code, il existe de nombreux principes de programmation que nous pouvons utiliser comme lignes directrices. Ci-dessous, je vais énumérer les plus importants.
  • • KISS – signifie « Keep It Simple, Stupid ». Vous remarquerez peut-être qu'au début de leur parcours, les développeurs tentent de mettre en œuvre des conceptions complexes et ambiguës.
  • • SEC – « Ne vous répétez pas. » Essayez d'éviter les doublons et placez-les plutôt dans une seule partie du système ou de la méthode.
  • YAGNI - "Vous n'en aurez pas besoin." Si vous commencez soudainement à vous demander : « Et si vous en ajoutiez davantage (fonctionnalités, code, etc.) ? », alors vous devrez probablement vous demander si cela vaut réellement la peine de les ajouter.
  • Un code propre au lieu d'un code intelligent - En termes simples, laissez votre ego à la porte et oubliez d'écrire du code intelligent. Vous voulez du code propre, pas du code intelligent.
  • Évitez l'optimisation prématurée – Le problème de l'optimisation prématurée est que vous ne savez jamais où se trouveront les goulots d'étranglement dans le programme jusqu'à ce qu'ils apparaissent.
  • Responsabilité unique – Chaque classe ou module d'un programme ne doit se soucier que de fournir un élément d'une fonctionnalité spécifique.
  • Composition plutôt qu'héritage d'implémentation - Les objets au comportement complexe doivent contenir des instances d'objets au comportement individuel, plutôt que d'hériter d'une classe et d'ajouter de nouveaux comportements.
  • La gymnastique d'objets est un exercice de programmation conçu selon un ensemble de 9 règles .
  • Échouer rapidement, arrêter rapidement - Ce principe signifie arrêter l'opération en cours lorsqu'une erreur inattendue se produit. Le respect de ce principe conduit à un fonctionnement plus stable.

Paquets

  1. Prioriser la structuration des packages par domaine plutôt que par niveau technique.
  2. Privilégiez les mises en page qui favorisent l’encapsulation et la dissimulation des informations pour se protéger contre les abus plutôt que d’organiser les cours pour des raisons techniques.
  3. Traitez les packages comme s'ils avaient une API immuable - n'exposez pas de mécanismes internes (classes) destinés uniquement au traitement interne.
  4. N’exposez pas les classes destinées à être utilisées uniquement dans le package.

Des classes

Statique

  1. N'autorisez pas la création d'une classe statique. Créez toujours un constructeur privé.
  2. Les classes statiques doivent rester immuables, ne pas autoriser les sous-classes ou les classes multithread.
  3. Les classes statiques doivent être protégées des changements d'orientation et doivent être fournies en tant qu'utilitaires tels que le filtrage de liste.

Héritage

  1. Choisissez la composition plutôt que l’héritage.
  2. Ne définissez pas de champs protégés . Spécifiez plutôt une méthode d’accès sécurisée.
  3. Si une variable de classe peut être marquée comme final , faites-le.
  4. Si l'héritage n'est pas attendu, rendez la classe final .
  5. Marquez une méthode comme finale si l'on ne s'attend pas à ce que les sous-classes soient autorisées à la remplacer.
  6. Si un constructeur n'est pas requis, ne créez pas de constructeur par défaut sans logique d'implémentation. Java fournira automatiquement un constructeur par défaut si aucun n'est spécifié.

Interfaces

  1. N'utilisez pas le modèle d'interface de constantes car il permet aux classes d'implémenter et de polluer l'API. Utilisez plutôt une classe statique. Cela présente l'avantage supplémentaire de vous permettre d'effectuer une initialisation d'objet plus complexe dans un bloc statique (comme remplir une collection).
  2. Évitez d'abuser de l'interface .
  3. Avoir une et une seule classe qui implémente une interface entraînera probablement une surutilisation des interfaces et fera plus de mal que de bien.
  4. "Programme pour l'interface, pas pour l'implémentation" ne signifie pas que vous devez regrouper chacune de vos classes de domaine avec une interface plus ou moins identique, en faisant cela vous cassez YAGNI .
  5. Gardez toujours les interfaces petites et spécifiques afin que les clients ne connaissent que les méthodes qui les intéressent. Consultez le FAI de SOLID.

Finalisateurs

  1. L' objet #finalize() doit être utilisé judicieusement et uniquement comme moyen de protection contre les échecs lors du nettoyage des ressources (comme la fermeture d'un fichier). Fournissez toujours une méthode de nettoyage explicite (telle que close() ).
  2. Dans une hiérarchie d'héritage, appelez toujours le finalize() du parent dans un bloc try . Le nettoyage de la classe devrait être dans un bloc final .
  3. Si une méthode de nettoyage explicite n'a pas été appelée et que le finaliseur a fermé les ressources, enregistrez cette erreur.
  4. Si aucun enregistreur n'est disponible, utilisez le gestionnaire d'exceptions du thread (qui finit par transmettre une erreur standard capturée dans les journaux).

Règles générales

Déclarations

Une assertion, généralement sous la forme d'une vérification de précondition, applique un contrat « échouer rapidement, arrêter rapidement ». Ils doivent être largement utilisés pour identifier les erreurs de programmation aussi près que possible de leur cause. Etat de l'objet :
  • • Un objet ne doit jamais être créé ou placé dans un état invalide.
  • • Dans les constructeurs et les méthodes, décrivez et appliquez toujours le contrat à l'aide de tests.
  • • Le mot-clé Java assert doit être évité car il peut être désactivé et constitue généralement une construction fragile.
  • • Utilisez la classe utilitaire Assertions pour éviter les conditions if-else verbeuses pour les vérifications de préconditions.

Génériques

Une explication complète et extrêmement détaillée est disponible dans la FAQ Java Generics . Vous trouverez ci-dessous des scénarios courants dont les développeurs doivent être conscients.
  1. Dans la mesure du possible, il est préférable d'utiliser l'inférence de type plutôt que de renvoyer la classe/interface de base :

    
    // MySpecialObject o = MyObjectFactory.getMyObject();
    public  T getMyObject(int type) {
    return (T) factory.create(type);
    }

  2. Si le type ne peut pas être déterminé automatiquement, insérez-le.

    public class MySpecialObject extends MyObject {
     public MySpecialObject() {
      super(Collections.emptyList());   // This is ugly, as we loose type
      super(Collections.EMPTY_LIST();    // This is just dumb
      // But this is beauty
      super(new ArrayList());
      super(Collections.emptyList());
     }
    }

  3. Caractères génériques :

    Utilisez un caractère générique étendu lorsque vous obtenez uniquement des valeurs d'une structure, utilisez un super caractère générique lorsque vous mettez uniquement des valeurs dans une structure et n'utilisez pas de caractère générique lorsque vous faites les deux.

    1. Tout le monde aime PECS ! ( Producteur-étend, Consommateur-super )
    2. Utilisez Foo pour le producteur T.
    3. Utilisez Foo pour le consommateur T.

Célibataires

Un singleton ne doit jamais être écrit dans le style de modèle de conception classique , ce qui est bien en C++ mais pas approprié en Java. Même s'il est correctement thread-safe, n'implémentez jamais ce qui suit (ce serait un goulot d'étranglement en termes de performances !) :

public final class MySingleton {
  private static MySingleton instance;
  private MySingleton() {
    // singleton
  }
  public static synchronized MySingleton getInstance() {
    if (instance == null) {
      instance = new MySingleton();
    }
    return instance;
  }
}
Si une initialisation paresseuse est vraiment souhaitée, alors une combinaison de ces deux approches fonctionnera.

public final class MySingleton {
  private MySingleton() {
   // singleton
  }
  private static final class MySingletonHolder {
    static final MySingleton instance = new MySingleton();
  }  
  public static MySingleton getInstance() {
    return MySingletonHolder.instance;
  }
}
Spring : Par défaut, un bean est enregistré avec une portée singleton, ce qui signifie qu'une seule instance sera créée par le conteneur et connectée à tous les consommateurs. Cela fournit la même sémantique qu’un singleton classique, sans aucune limitation de performances ni de liaison.

Des exceptions

  1. Utilisez des exceptions vérifiées pour les conditions corrigibles et des exceptions d'exécution pour les erreurs de programmation. Exemple : obtenir un entier à partir d'une chaîne.

    Mauvais : NumberFormatException étend RuntimeException, il est donc destiné à indiquer des erreurs de programmation.

  2. Ne faites pas ce qui suit :

    
    // String str = input string
    Integer value = null;
    try {
       value = Integer.valueOf(str);
    } catch (NumberFormatException e) {
    // non-numeric string
    }
    if (value == null) {
    // handle bad string
    } else {
    // business logic
    }

    Utilisation correcte :

    
    // String str = input string
    // Numeric string with at least one digit and optional leading negative sign
    if ( (str != null) && str.matches("-?\\d++") ) {  
       Integer value = Integer.valueOf(str);
      // business logic
    } else {
      // handle bad string
    }
  3. Vous devez gérer les exceptions au bon endroit, au bon endroit au niveau du domaine.

    MAUVAISE MANIÈRE - La couche d'objets de données ne sait pas quoi faire lorsqu'une exception de base de données se produit.

    
    class UserDAO{
        public List getUsers(){
            try{
                ps = conn.prepareStatement("SELECT * from users");
                rs = ps.executeQuery();
                //return result
            }catch(Exception e){
                log.error("exception")
                return null
            }finally{
                //release resources
            }
        }}
    

    VOIE RECOMMANDÉE - La couche de données doit simplement renvoyer l'exception et transférer la responsabilité de la gestion ou non de l'exception à la bonne couche.

    
    === RECOMMENDED WAY ===
    Data layer should just retrow the exception and transfer the responsability to handle the exception or not to the right layer.
    class UserDAO{
       public List getUsers(){
          try{
             ps = conn.prepareStatement("SELECT * from users");
             rs = ps.executeQuery();
             //return result
          }catch(Exception e){
           throw new DataLayerException(e);
          }finally{
             //release resources
          }
      }
    }

  4. Les exceptions ne doivent généralement PAS être enregistrées au moment où elles sont émises, mais plutôt au moment où elles sont effectivement traitées. Les exceptions de journalisation, lorsqu'elles sont lancées ou relancées, ont tendance à remplir les fichiers journaux de bruit. Notez également que la trace de la pile d'exceptions enregistre toujours l'endroit où l'exception a été levée.

  5. Prend en charge l’utilisation d’exceptions standard.

  6. Utilisez des exceptions plutôt que des codes retour.

Égal à et HashCode

Il existe un certain nombre de problèmes à prendre en compte lors de l'écriture de méthodes d'équivalence d'objet et de code de hachage appropriées. Pour faciliter son utilisation, utilisez les égal et hash de java.util.Objects .

public final class User {
 private final String firstName;
 private final String lastName;
 private final int age;
 ...
 public boolean equals(Object o) {
   if (this == o) {
     return true;
   } else if (!(o instanceof User)) {
     return false;
   }
   User user = (User) o;
   return Objects.equals(getFirstName(), user.getFirstName()) &&                                
    Objects.equals(getLastName(),user.getLastName()) &&
    Objects.equals(getAge(), user.getAge());
 }
 public int hashCode() {
   return Objects.hash(getFirstName(),getLastName(),getAge());
 }
}

La gestion des ressources

Moyens de libérer des ressources en toute sécurité : l' instruction try-with-resources garantit que chaque ressource est fermée à la fin de l'instruction. Tout objet qui implémente java.lang.AutoCloseable, qui inclut tous les objets qui implémentent java.io.Closeable , peut être utilisé comme ressource.

private doSomething() {
try (BufferedReader br = new BufferedReader(new FileReader(path))) 
 try {
   // business logic
 }
}

Utiliser des crochets d'arrêt

Utilisez un hook d'arrêt appelé lorsque la JVM s'arrête correctement. (Mais il ne sera pas capable de gérer les interruptions soudaines, telles que dues à une panne de courant.) Il s'agit d'une alternative recommandée au lieu de déclarer une méthode finalize() qui ne s'exécutera que si System.runFinalizersOnExit() est vrai (la valeur par défaut est false) .

public final class SomeObject {
 var distributedLock = new ExpiringGeneralLock ("SomeObject", "shared");
 public SomeObject() {
   Runtime
     .getRuntime()
     .addShutdownHook(new Thread(new LockShutdown(distributedLock)));
 }
 /** Code may have acquired lock across servers */
 ...
 /** Safely releases the distributed lock. */
 private static final class LockShutdown implements Runnable {
   private final ExpiringGeneralLock distributedLock;
   public LockShutdown(ExpiringGeneralLock distributedLock) {
     if (distributedLock == null) {
       throw new IllegalArgumentException("ExpiringGeneralLock is null");
     }
     this.distributedLock = distributedLock;
   }
   public void run() {
     if (isLockAlive()) {
       distributedLock.release();
     }
   }
   /** @return True if the lock is acquired and has not expired yet. */
   private boolean isLockAlive() {
     return distributedLock.getExpirationTimeMillis() > System.currentTimeMillis();
   }
 }
}
Permettez aux ressources de devenir complètes (et renouvelables) en les répartissant entre les serveurs. (Cela permettra la récupération après une interruption soudaine telle qu'une panne de courant.) Voir l'exemple de code ci-dessus qui utilise ExpiringGeneralLock (un verrou commun à tous les systèmes).

Date-Heure

Java 8 introduit la nouvelle API Date-Time dans le package java.time. Java 8 introduit une nouvelle API Date-Heure pour remédier aux lacunes suivantes de l'ancienne API Date-Heure : non-threading, mauvaise conception, gestion complexe des fuseaux horaires, etc.

Parallélisme

Règles générales

  1. Méfiez-vous des bibliothèques suivantes, qui ne sont pas thread-safe. Synchronisez-vous toujours avec les objets s'ils sont utilisés par plusieurs threads.
  2. Date ( non immuable ) - Utilisez la nouvelle API Date-Time, qui est thread-safe.
  3. SimpleDateFormat - Utilisez la nouvelle API Date-Time, qui est thread-safe.
  4. Préférez utiliser les classes java.util.concurrent.atomic plutôt que de rendre les variables volatiles .
  5. Le comportement des classes atomiques est plus évident pour le développeur moyen, alors que volatile nécessite une compréhension du modèle de mémoire Java.
  6. Les classes atomiques enveloppent les variables volatiles dans une interface plus pratique.
  7. Comprendre les cas d'utilisation où volatile est approprié . (voir article )
  8. Utiliser appelable lorsqu'une exception vérifiée est requise mais qu'il n'y a pas de type de retour. Puisque Void ne peut pas être instancié, il communique l'intention et peut renvoyer null en toute sécurité .

Ruisseaux

  1. java.lang.Thread devrait être obsolète. Bien que ce ne soit pas officiellement le cas, dans presque tous les cas, le package java.util.concurrent fournit une solution plus claire au problème.
  2. L'extension de java.lang.Thread est considérée comme une mauvaise pratique - implémentez plutôt Runnable et créez un nouveau thread avec une instance dans le constructeur (règle de composition sur l'héritage).
  3. Préférez les exécuteurs et les threads lorsqu’un traitement parallèle est requis.
  4. Il est toujours recommandé de spécifier votre propre fabrique de threads personnalisée pour gérer la configuration des threads créés ( plus de détails ici ).
  5. Utilisez DaemonThreadFactory dans Executors pour les threads non critiques afin que le pool de threads puisse être arrêté immédiatement lorsque le serveur s'arrête ( plus de détails ici ).

this.executor = Executors.newCachedThreadPool((Runnable runnable) -> {
   Thread thread = Executors.defaultThreadFactory().newThread(runnable);
   thread.setDaemon(true);
   return thread;
});
  1. La synchronisation Java n'est plus aussi lente (55 à 110 ns). Ne l'évitez pas en utilisant des astuces comme le verrouillage à double vérification .
  2. Préférez la synchronisation avec un objet interne plutôt qu'une classe, puisque les utilisateurs peuvent se synchroniser avec votre classe/instance.
  3. Synchronisez toujours plusieurs objets dans le même ordre pour éviter les blocages.
  4. La synchronisation avec une classe ne bloque pas automatiquement l'accès à ses objets internes. Utilisez toujours les mêmes verrous lorsque vous accédez à une ressource.
  5. N'oubliez pas que le mot-clé synchronisé n'est pas considéré comme faisant partie de la signature de la méthode et ne sera donc pas hérité.
  6. Évitez une synchronisation excessive, cela peut entraîner de mauvaises performances et un blocage. Utilisez le mot-clé synchronisé uniquement pour la partie du code qui nécessite une synchronisation.

Collections

  1. Utilisez les collections parallèles Java-5 dans du code multithread autant que possible. Ils sont sûrs et possèdent d’excellentes caractéristiques.
  2. Si nécessaire, utilisez CopyOnWriteArrayList au lieu de synchroniséList.
  3. Utilisez Collections.unmodifiable list(...) ou copiez la collection lors de sa réception en tant que paramètre dans new ArrayList(list) . Évitez de modifier les collections locales en dehors de votre classe.
  4. Renvoyez toujours une copie de votre collection, en évitant de modifier votre liste en externe avec new ArrayList (list) .
  5. Chaque collection doit être encapsulée dans une classe distincte, donc désormais le comportement associé à la collection a un foyer (par exemple, méthodes de filtrage, application d'une règle à chaque élément).

Divers

  1. Choisissez les lambdas plutôt que les classes anonymes.
  2. Choisissez des références de méthode plutôt que des lambdas.
  3. Utilisez des énumérations au lieu de constantes int.
  4. Évitez d'utiliser float et double si des réponses précises sont requises, utilisez plutôt BigDecimal tel que Money.
  5. Choisissez des types primitifs plutôt que des primitives encadrées.
  6. Vous devez éviter d'utiliser des nombres magiques dans votre code. Utilisez des constantes.
  7. Ne retournez pas Null. Communiquez avec votre client de méthode en utilisant « Facultatif ». Idem pour les collections - renvoie des tableaux ou des collections vides, pas des valeurs nulles.
  8. Évitez de créer des objets inutiles, réutilisez des objets et évitez un nettoyage inutile du GC.

Initialisation paresseuse

L'initialisation paresseuse est une optimisation des performances. Il est utilisé lorsque les données sont considérées comme « coûteuses » pour une raison quelconque. Dans Java 8, nous devons utiliser l'interface du fournisseur fonctionnel pour cela.

== Thread safe Lazy initialization ===
public final class Lazy {
   private volatile T value;
   public T getOrCompute(Supplier supplier) {
       final T result = value; // Just one volatile read
       return result == null ? maybeCompute(supplier) : result;
   }
   private synchronized T maybeCompute(Supplier supplier) {
       if (value == null) {
           value = supplier.get();
       }
       return value;
   }
}
Lazy lazyToString= new Lazy<>()
return lazyToString.getOrCompute( () -> "(" + x + ", " + y + ")");
C'est tout pour le moment, j'espère que cela vous a été utile !
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION