jeudi 22 avril 2010

Java - Bonnes pratiques


Je rassemble ici en ensemble de bonnes pratiques trouvées dans mes périgrinations.

Les classes interdites

Lors d’utilisation de classe provenant d’un SDK, il faut s’assurer qu’elles correspondent bien a nos besoins et qu’il n’y a pas plus « léger » à utiliser. Certaines classes ne sont la que pour assurer la compatibilité ascendante. On en exclura donc leur usage sauf si leur remplacent ne satisfont pas au besoin.

Exemple de classes "interdites" :
  • Hashtable : classe synchronisée => Thread-safe mais lourde (rapport 1 à 5)
    Utiliser plutôt dans une utilisation standard : HashMap
    Si besoin d’un conteneur thread-Safe utiliser : Collections.synchronizedMap
  • Vector : classe synchronisée => Thread-safe mais lourde (rapport 1 à 5)
    Utiliser plutôt dans une utilisation standard : ArrayList
    Si besoin d’un conteneur thread-Safe utiliser : Collections.synchronizedList

Concaténation de chaînes de caractères

Malgré ce que l’on pense, le code suivant n’est pas mauvais ; il est même optimum :
String s = "In memory-constrained situations" +
           ", you should pay close attention" +
           " to the" +
           " proliferation " +
           " of"+
           " strings";

En effet, le compilateur va optimiser l’expression en la transformant en une seule chaîne. De même que ce code :
String myValue = “Ma valeur à afficher”;
String s = “You should pay close attention" +
         " to the" +
         " proliferation " +
         "of"+ myValue;

Par contre celui-ci est plus coûteux car le compilateur ne sait pas l’optimiser. Et, il sera à éviter.
String s = "In memory-constrained situations";
s+= ", you should pay close attention";
s+= " to the";
s+= " proliferation ";
s+= "of";
s+= " strings";

Il est possible d’utiliser un StringBuilder qui sera moins couteux et plus flexible lorsque le 1er cas ne peut être suivi :
StringBuilder sb = new StringBuilder() ;
Sb.add("In memory-constrained situations") ;
Sb.add(", you should pay close attention") ;
Sb.add(" to the") ;
Sb.add(" proliferation ") ;
Sb.add("of") ;
Sb.add(" strings") ;

Classes retournant des collections.

Beaucoup de classe retournent des collections (List, Map,...). Ces collections doivent toujours être initialisées. S’il n’y a pas d’objet dans la collection celle-ci doit être vide mais non null.

Les tableaux fourretouts

Il faut éviter les tableaux fourretouts de type Object[]. Cela rend le code illisible et compliqué à utiliser (quel objet vais-je trouver dans le tableau ? Comment dois-je construire ce tableau ?) et source d’erreur vu qu’il n’y a pas de typage. Il est préférable de créer une classe « Bean » rassemblant les objets composant ce tableau.

Exceptions

Ne jamais ignorer une exception
Une erreur fréquente est de mettre un bloc catch vide sans aucune instruction afin de pouvoir compiler le programme. Ceci est très dangereux. En effet, si une exception survient, elle sera passée sous silence et le programme continuera de fonctionner ce qui peut déboucher sur des bugs incompréhensibles. Le bon réflexe est de bien traiter les exceptions dans les blocs catch. Mettre un commentaire clair si c’est normal de ne rien faire.

Reflet de l’erreur
Les exceptions doivent refléter l’erreur levée. Trouvez la bonne granularité : une interface graphique se moque de savoir que c’est la base de données qui ne répond pas. Elle a juste besoin de savoir que le service est momentanément interrompu. Le niveau de précision (la granularité) de l’exception doit être adapté à la position du module dans une architecture en couche.

Les exceptions génériques
Eviter de catcher les exceptions génériques du style :
Try {
     .....
} catch (Exception e) {
     .....
}

Il est préférable de catcher exception par exception et de faire le traitement spécifique. Cela permet un débogage plus facile en cas de problème.

De même, il est déconseillé de renvoyer une exception générique. Il est préférable de créer des exceptions par package ou d’utiliser les exceptions fournis dans le SDK, cela permet, sur une trace, de retrouver plus facilement les causes de l’exception levée.

Les exceptions et les entrées/sorties
En cas d’exceptions, il faut s’assurer que l’état après le catch est stable et qu’il ne reste rien d’ouvert (fichier, flux, connexion,...).
Il faut donc utiliser le « Finally » pour libérer les ressources.

Exemple java :
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class FluxMajuscule {

/** Crée une nouvelle instance de FluxMajuscule */
  public FluxMajuscule() {
  }

  public void readMaj(){
    try{
      BufferedReader br=new BufferedReader(new FileReader("monFichier.txt"));
        try{
          String ligne;
          String ligneMajuscule;
          while((ligne=br.readLine())!=null){
            ligneMajuscule=ligne.toUpperCase();
            System.out.println(ligneMajuscule);
          }
        }finally{
          br.close();
        }
      }catch(IOException ex){
        ex.printStackTrace();
      }
    }
}

Manifest

En Java, il est conseillé de remplir le manifeste de façon standard. Le SDK fournit des méthodes pour aller lire ce manifeste dans des packages.

Exemple de manifest java :
Manifest-version: 1.0
Implementation-Version: 1.2

Specification-Title: MaLibrary
Specification-Version: 1.2
Specification-Vendor: MaSociété
Implementation-Vendor: MaSociété
Implementation-Title: MaLibrary
Implementation-Version: 1.2
Build-date: 2009-09-07 19:01:52

Thread

Synchronisation
La mise en place d’un processus de synchronisation est tres gourmant en ressource. Du coup avant de se lancer dans la synchronisation, on doit se poser quelques questions :
  • A-t-on vraiment besoin d’avoir une méthode synchronisée ? (Utilisation de ressource synchrone ? Ordre d’exécution important ?...)
  • Cette méthode peut-elle être utilisée en multi-thread ?

Le mot clé « synchronized » en java permet de définir des sections critiques thread safe. Il est souvent utilisé avec la déclaration de la méthode d’une classe.

public synchronized void theMethod() {}

Cette écriture est équivalente à
public void theMethod() {
  synchronized (this) {
    …
  }
}

Plutôt que d’utiliser la synchronisation au niveau de la méthode, il est préférable de l’utiliser uniquement sur la section de la méthode qui nécessite un accès thread safe. D’ailleurs, rien n’oblige le développeur à utiliser « this » comme objet de synchronisation. On peut par exemple utiliser un singleton si l’on souhaite synchroniser plusieurs méthodes disséminées dans plusieurs objets.
Le principal reproche que l’on puisse faire à la technique du synchronized, c’est le manque de granularité. En effet, si un système multi thread tente de lire et de modifier une variable en même temps, le verrou sera le même pour la lecture ainsi que pour l’écriture, provoquant un goulot d’étranglement.

Pour palier a cela, il est possible d’utiliser des verrous d’écriture et de lecture, mais le code s’en retrouve complexifié.
Un verrou de lecture est différent d’un verrou d’écriture. Le verrou d’écriture doit attendre que les verrous de lecture soient tous levés pour être posé. Plusieurs verrous de lecture peuvent être posés en même temps alors qu’un seul verrou d’écriture ne peut être posé à un instant donné.

Aucun commentaire :

Enregistrer un commentaire