Anti-Pattern : Déclaration sauvage

Olivier Capuozzo

28-Avril-2003


Table des matières

Confondre variables locales et variables d'instance
Exemple de mauvaise pratique
Mauvaise pratique ? Pourquoi ?
Éléments de connaissance
Niveau conceptuel
Attribut ou variable locale ?
Niveau technique
Solution
Principe de mise en oeuvre
Conclusion

Confondre variables locales et variables d'instance

On constate trop souvent la présence de variables dans la zone de portée d'instance alors qu'elles ne sont, de part leur rôle, que des variables locales, à durée de vie très courte.

Exemple de mauvaise pratique

Ce programme détermine un âge en fonction d'une année de naissance transmise par l'utilisateur.

1  : import java.awt.event.*;
2  : import java.awt.*;
3  : import javax.swing.*;
4  : import javax.swing.event.*;
5  : import javax.swing.text.*;
6  : import java.util.*;
7  :
8  : class Age extends JFrame implements DocumentListener, WindowListener {
9  : 
10 : JTextField tfDateNaissance;
11 : JLabel lbAge = new JLabel();
12 : Document doc;
13 : Container contentPane;
14 : JPanel panel = null;
15 : 
16 : public Age() {
17 :    super("Age");
18 :    setSize(300, 200);
19 :    tfDateNaissance = new JTextField(9);
20 :    panel = new JPanel();
21 :    contentPane = getContentPane();
22 :    panel.add(tfDateNaissance);
23 :    contentPane.add(panel, BorderLayout.NORTH);
24 :    panel = new JPanel();
25 :    panel.add(lbAge);
26 :    contentPane.add(panel, BorderLayout.CENTER);
27 :  
28 :    doc = tfDateNaissance.getDocument();
29 :    doc.addDocumentListener(this);
30 :    addWindowListener(this);
31 :    calculerAge();
32 :  }
33 :  
34 :  public void calculerAge() {
35 :    Calendar now = Calendar.getInstance();
36 :    try {
37 :      int ann_nais = Integer.parseInt(tfDateNaissance.getText());
38 :      String age = String.valueOf(now.get(Calendar.YEAR)-ann_nais);
39 :      lbAge.setText("Vous avez : " + age + " ans.");
40 :    }
41 :    catch (NumberFormatException e) {
42 :      lbAge.setText("Entrez votre année de naissance");
43 :   }
44 :  }
45 :
46 : // Evenements générés via le composant JTextField
47 : public void insertUpdate(DocumentEvent e)  {  calculerAge(); }
48 : public void removeUpdate(DocumentEvent e)  {  calculerAge(); }
49 : public void changedUpdate(DocumentEvent e) {  calculerAge(); }
50 :  
51 : // Evenements de fenetre
52 : public void windowClosing(WindowEvent e)     { System.exit(0); }
53 : public void windowActivated(WindowEvent e)   {}
54 : public void windowClosed(WindowEvent e)      {}
55 : public void windowDeactivated(WindowEvent e) {}
56 : public void windowDeiconified(WindowEvent e) {}
57 : public void windowIconified(WindowEvent e)   {}
58 : public void windowOpened(WindowEvent e)      {}
59 :  
60 : }//Age
61 :  
62 : public class AppAge {
63 :  
64 :  public static void main(String[] args) {
65 :    Age age = new Age();
66 :    age.show();
66 :  }
67 :  }//AppAge
  

Exemple d'exécutions

Mauvaise pratique ? Pourquoi ?

Le problème se situe entre les lignes 10 et 14, à savoir la partie déclaration des variables d'instance complétée par le corps du constructeur.

  • Choix d'une politique d'initialisation

    10 : JTextField tfDateNaissance;
    11 : JLabel lbAge = new JLabel();
                     

    Manque de cohérence dans l'initialisation de ces deux variables d'instance. tfDateNaissance sera initialisée dans le corps du constructeur, alors que lbAge le sera avant l'exécution du corps du constructeur.

  • Fausses variables d'instance

    12 : Document doc;
    13 : Container contentPane;
    14 : JPanel panel = null;
    

    Ces trois variables ont-elles réellement le statut de variables d'instance ?

    Un coup d'oeil dans le corps du constructeur sur leur rôle nous permet d'affirmer qu'elles n'ont que le statut de variables locales.

    Au passage, soulignons que

    • La variable panel sera initialisée deux fois (voir page suivante).

    • Seules les variables tfDateNaissance et lbAge sont utilisées par les méthodes de la classe, ce qui conforte notre analyse.

Éléments de connaissance

Niveau conceptuel

Une classe est composée de trois types d'éléments fortement liés à ses instances : attributs, méthodes et constructeurs - nous mettons sous silence le cas particulier des propriétés de classe (celles marquées static).

A chacun de ces éléments est associé un attribut de visibilité parmi package, private, protected et public, et une portée (instance ou classe).

  • private

    Réservé aux méthodes d'implémentation, suite à un découpage fonctionnel.

  • package

    Elément visible par les objets appartenant au même package. C'est la valeur par défaut.

  • protected

    Caché du monde extérieur mais visible aux objets des classes descendantes.

  • public

    Réservé d'ordinaire aux méthodes qui représentent un service offert par l'objet.

On l'aura compris, en l'absence d'attribut de visibilité, le langage décide pour vous (visibilité package).

Il est donc recommandé de mentionner systématiquement la visibilité des attributs et méthodes. Cette pratique a le mérite d'aider le développeur à se poser de bonnes questions.

Attribut ou variable locale ?

Quelle différence y a-t-il entre un attribut (appelé aussi champ) et une variable locale ?

2 différences majeures portant sur la durée de vie et la portée :

  • Durée de vie

    • Locale (déclaration dans le corps d'une méthode)

      Une variable locale a une durée qui commence à sa déclaration et se poursuit dans la limite du bloc (par exemple une méthode) dans lequel elle est déclarée.

    • Instance (déclaration hors du corps d'une méthode)

      La durée de vie est celle de l'objet dont elle est un constituant.

    • Classe (déclaration hors du corps d'une méthode, mentionnée static)

      La durée de vie est celle de la classe, considérée alors comme un objet. Bien que connue des instances (objets) de la classe à laquelle elle est liée, sa durée de vie transcende celles de ces derniers.

  • Portée/Visibilité

    • Locale (voir ci-dessus)

      N'est connue qu'à l'intérieur du bloc où elle est déclarée (à partir de sa déclaration).

    • Instance (voir ci-dessus)

      Accessible par toutes les méthodes de la classe, quelque soit leur attribut de visibilité.

      Pour le reste (objets de classes utilisatrices ou de classes héritées) la visibilité est fonction de l'attribut de visibilité.

    • Classe (voir ci-dessus)

      Accessible via une référence à la classe à laquelle elle est liée (mais aussi via une référence à un objet de cette classe, pratique non recommandée).

Niveau technique

En java, contrairement aux variables locales, les variables d'instance sont initialisées à leur valeur par défaut (qui dépend du type de la variable).

Bon à savoir

A la création d'un objet, le processus d'initialisation des variables est le suivant :

  1. Initialisation des variables d'instance à leur valeur par défaut (selon leur type).

  2. Appel du constructeur du parent.

  3. Initialisation des variables d'instance (instructions d'affectaton placées à la déclaration)

  4. Exécution du corps du constructeur concerné.

Solution

Principe de mise en oeuvre

=> Recherche d'un couplage fort entre variables d'instance et méthodes.

Les variables d'instance sont utilisées, en général, par plusieurs méthodes. Ces variables sont d'ordinaire en liaison directe avec les services offerts par l'instance.

Les variables d'instance étant initialisées à leur valeur par défaut (selon leur type), une bonne pratique est de les valoriser dans le corps du constructeur. Ces opérations peuvent être regroupées dans une méthode dont c'est le seul rôle (et souvent nommée init()). Exemple :

01 : import java.awt.event.*;
02 : import java.awt.*;
03 : import javax.swing.*;
04 : import javax.swing.event.*;
05 : import javax.swing.text.*;
06 : import java.util.*;
07 :  
08 : class Age extends JFrame implements DocumentListener, WindowListener {
09 :  
10 :  private JTextField tfDateNaissance;
11 :  private JLabel lbAge;
12 :  
13 :  public Age() {
14 :    super("Age");
15 :    init();
16 :    calculerAge();
17 :  }
18 :  
19 : private void init() {
21 :    setSize(300, 200);
22 :    tfDateNaissance = new JTextField(9);
23 :    lbAge = new JLabel();
24 :  
25 :    // utilisation de variables pour des besoins locaux
26 :    Container contentPane = getContentPane();
27 :    JPanel panel = new JPanel();
28 :    panel.add(tfDateNaissance);
29 :    contentPane.add(panel, BorderLayout.NORTH);
30 :  
31 :    panel = new JPanel();
32 :    panel.add(lbAge);
33 :    contentPane.add(panel, BorderLayout.CENTER);
34 :  
35 :    Document doc = tfDateNaissance.getDocument();
36 :    doc.addDocumentListener(this);
37 :    addWindowListener(this);
38 :  }
39 :  
40 :  public void calculerAge() {
41 :    Calendar now = Calendar.getInstance();
42 :    try {
43 :      int ann_nais = Integer.parseInt(tfDateNaissance.getText());
44 :      String age = String.valueOf(now.get(Calendar.YEAR)-ann_nais);
45 :      lbAge.setText("Vous avez : " + age + " ans.");
46 :    }
47 :    catch (NumberFormatException e) {
48 :      lbAge.setText("Entrez votre année de naissance");
49 :    }
50 :  }
51 :  
52 : // Evenements générés via le composant JTextField
53 : public void insertUpdate(DocumentEvent e)  {  calculerAge(); }
54 : public void removeUpdate(DocumentEvent e)  {  calculerAge(); }
55 : public void changedUpdate(DocumentEvent e) {  calculerAge(); }
56 :  
57 : // Evenements de fenetre
58 : public void windowClosing(WindowEvent e)     { System.exit(0); }
59 : public void windowActivated(WindowEvent e)   {}
60 : public void windowClosed(WindowEvent e)      {}
61 : public void windowDeactivated(WindowEvent e) {}
62 : public void windowDeiconified(WindowEvent e) {}
63 : public void windowIconified(WindowEvent e)   {}
64 : public void windowOpened(WindowEvent e)      {}
65 :  
66 : }//Age
67 :  
68 : public class CorrAppAge {
69 :  
70 :  public static void main(String[] args) {
71 :    Age age = new Age();
72 :    age.show();
73 :  }
  

Conclusion

Il peut arriver parfois, sous la contrainte du temps, de réaliser involontairement des «déclarations sauvages» de variables.

Il est du devoir du développeur de relire son travail et de ne pas se contenter d'un « c'est bon si ça tourne ».

La qualité interne d'un programme nécessite très souvent la réécriture d'une partie du code; on change la forme sans rien changer aux fonctionnalités du logiciel. Cette activité, reconnue dans le métier, porte le nom de refactoring.

Un code source cohérent, lisible, esthétique, se maintiendra bien mieux qu'un code sale, c'est à dire créée avec peu de soins. Certains développeurs (eXtreme Programming) disent que le code a une odeur : S'il sent mauvais, il doit être nettoyé.