Quelles sont les bonnes conventions d'écriture pour Java SE 6.0 ?

Le , par ze_corsaire, Membre actif
Bonjour,

J'aimerais savoir si quelqu'un a connaissance de recommandation, préconisations dans le cadre de la programmation java en 1.6.
Je recherche un doc du type http://java.sun.com/docs/codeconv/CodeConventions.pdf un peu plus à jour, préconisant par exemple l'utilisation des boucles for améliorées, la pratique de l'autoboxing (ou pas), pour des raisons de perf, de lisibilité ou d'homogénéisation.

Merci.

______


Vous avez aimé cette actualité ? Alors partagez-la avec vos amis en cliquant sur les boutons ci-dessous :


 Poster une réponse

Avatar de Yomhgui Yomhgui - Membre régulier http://www.developpez.com
le 20/08/2009 à 19:29
Citation Envoyé par tchize_  Voir le message
CE cast est nécessaire car les generics ne sont pas garanti en cas de mauvais programmeur. Le generics t'évitent simplement de devoir le mettre explicitement partout dans ton code, le compilo le faisant à ta place.

Exemple simple

Code : Sélectionner tout
1
2
3
4
5
 
List<String> maList = ....; 
List l = maList; //warning compilateur 
l.add(new Integer(3)); 
String s = maList.get(0); // sans un cast dans le bytecode, la jvm va être dans le caca pour gérer ça ;)

Ouaip en effet, c'est bien un problème lié à la conservation des "raw types" et à la compatibilité avec d'anciennes versions du langage alors ? Ce problème que tu évoques ne pourrait pas se poser si l'utilisation d'un paramètre était obligatoire, où alors je ne vois pas de use case qui corresponde à ça.

Ceci dit, il y a une conséquence de ça qui me chagrine fortement. En définitive, l'utilisation des genercis aurait même plutôt tendance à dégrader le byte code.

Pour reprendre l'exemple que j'avais évoqué plus haut, celui avec I, CI1, CI2 et C. Dans cet exemple, de manière factuelle, l'utilisation de types paramétrés ne sert à rien. Par contre, quand on utilise C.get() : T qui retourne une référence du type paramétré dans C<T>, on se paye un cast alors que si on avait simplement utilisé un type I pour l'attribut de la classe C, on aurait eu le même comportement sans faire aucun cast en runtime cette fois.

En effet, d'après ce que j'ai pu voir dans le byte code, même si on déclare un paramètre de cette manière :

Code : Sélectionner tout
1
2
3
4
5
interface I {} 
 
class C<T extends I> { 
    private T _t; 
}
dans le code compilé, l'attribut de _t de C n'est pas de type I (ce qui semblerait pourtant théoriquement possible) mais de type Object.

Bon, au vu des (très grands) avantages en terme de contrôle de typage à la compilation, je continue à penser que ça vaut le coût de payer ce petit inconvénient, mais ça me semble quand même bien dommage.

Je vais quand même revérifier ça par acquis de conscience...

Bon, fausse alerte. Le type déclaré de l'attribut C._t est bien I, donc pas de problèmes de ce côté.
Avatar de adiGuba adiGuba - Expert éminent sénior http://www.developpez.com
le 20/08/2009 à 20:20
Citation Envoyé par Yomhgui  Voir le message
Ceci dit, j'avoue ne pas saisir (pour l'instant) le pourquoi de la chose. Ce cast est quand même totalement inutile puisque le type est connu.

Le type n'est pas connu justement !!!

Le "raw type" correspond au type de base de déclaration du generics. Par exemple :
Code : Sélectionner tout
1
2
3
4
5
6
7
class MaClass<T> {  
 // Le raw type de T est Object 
} 
 
class MaClass2<T extends Number> { 
  // Le raw type de T est Number 
}
Le raw-type correspond au type que tu utiliserais si la classe n'était pas paramétrée...

Citation Envoyé par Yomhgui  Voir le message
Ceci dit, il y a une conséquence de ça qui me chagrine fortement. En définitive, l'utilisation des genercis aurait même plutôt tendance à dégrader le byte code.

Non cela ne dégrade en rien le bytecode. Cela n'ajoute des casts que là où tu aurait dû les mettre.
Par contre, si tout compile sans warning, cela te garantie qu'aucune ClassCastException ne viendra pourrir ton exécution

Il y a plusieurs raisons à tout cela, dont la principale étant la compatibilité...
Pour plus d'info : Les Generics ne sont pas des Templates comme les autres !

a++
Avatar de Yomhgui Yomhgui - Membre régulier http://www.developpez.com
le 20/08/2009 à 20:44
OK, ce sont bien les conclusions auxquelles j'étais arrivé (à la fin des fins !!!).

Ce qui me surprend, c'est quand tu dis que le type n'est pas connu. De manière factuelle, si le type est connu lors de la compilation, il n'y a pas de raison qu'il ne le soit pas en runtime. C'est quand même bien dommage justement de ne pas pouvoir profiter du fait que l'on peut effectivement bénéficier du type checking pour résoudre le typage de manière déterministe et ne pas conserver ces données dans le code compilé !! Compatibilité ascendante, quand tu nous tiens...

PS: en ce qui me concerne je n'accepte pas de code avec des "unchecked cast" (sauf cas particulier pourri où ça n'est pas possible de faire autrement évidemment), donc pas de warning.

PS: La définition du raw type dans le spécification du langage:

More precisely, a raw type is define to be either:

* The name of a generic type declaration used without any accompanying actual type parameters.
* Any non-static type member of a raw type R that is not inherited from a superclass or superinterface of R.

PS3: Je me souviens d'avoir trouvé un article très intéressant sur la question de l'allocation des tableaux génériques (l'impossibilité de...) mais j'ai ma dose pour ce soir. Je tacherai de le remettre en référence ici demain.
Avatar de adiGuba adiGuba - Expert éminent sénior http://www.developpez.com
le 20/08/2009 à 21:08
Citation Envoyé par Yomhgui  Voir le message
C'est quand même bien dommage justement de ne pas pouvoir profiter du fait que l'on peut effectivement bénéficier du type checking pour résoudre le typage de manière déterministe et ne pas conserver ces données dans le code compilé !!

Oui... mais 95% des fonctionnalités sont malgré tout présente

Citation Envoyé par Yomhgui  Voir le message
Compatibilité ascendante, quand tu nous tiens...

En même temps tu te voyais avec une API "Collections 2" incompatible avec l'API de Collections actuelles ???

Citation Envoyé par Yomhgui  Voir le message
PS3: Je me souviens d'avoir trouvé un article très intéressant sur la question de l'allocation des tableaux génériques (l'impossibilité de...) mais j'ai ma dose pour ce soir. Je tacherai de le remettre en référence ici demain.

Je n'ai plus en tête le problème exact... mais cela vient du fait que la vérification du typeage des Generics est fait à la compilation, alors que celui des tableau l'est à l'exécution.
Tu peux te retrouver dans certains cas avec des erreurs bien bizarre...

a++
Avatar de tchize_ tchize_ - Expert éminent sénior http://www.developpez.com
le 20/08/2009 à 21:50
Citation Envoyé par Yomhgui  Voir le message

Ce qui me surprend, c'est quand tu dis que le type n'est pas connu. De manière factuelle, si le type est connu lors de la compilation, il n'y a pas de raison qu'il ne le soit pas en runtime.

A la compilation tu connait le type d'une instance particulière, vue depuis l'extérieur de la classe. Quand tu compile la classe générique -> le type n'est pas connu. Quand tu passe ton instance generics à des méthodes qui prennent, par exemple un List<?>, tu perd aussi cette information. Les generics sont une aide à la compilation, la pluspart des infos relative au generics sont perdus à la compilation. Par exemple, si je fait

Code : Sélectionner tout
1
2
List<String> l = ...; 
Object o = l;
aucun code ne me permettra de récupérer les paramètres generics de "o", par contre un o.getClass().getName() me retournera bien "java.util.ArrayList" par exemple.
Avatar de gifffftane gifffftane - Membre émérite http://www.developpez.com
le 20/08/2009 à 23:35
Citation Envoyé par Yomhgui  Voir le message
PS3: Je me souviens d'avoir trouvé un article très intéressant sur la question de l'allocation des tableaux génériques (l'impossibilité de...) mais j'ai ma dose pour ce soir. Je tacherai de le remettre en référence ici demain.

Oui, c'est parce que le type générique est pas connu à l'exécution est que ça fiche le pataques.
Code : Sélectionner tout
1
2
3
4
5
6
7
 
// FAUX CODE 
Toto<Truc>[] totoTruc = new Toto<Truc>[10]; // ici le type à l'exécution est juste Toto[] 
Object[] oTotoTruc = tototruc; // valide 
oTotoTruc[0] = new Toto<Ouille>(); // réussirait, puisque le seul type connu à l'exécution est Toto[]. 
// comme totoTruc est toujours adressable, on pourrait y lire des éléments non conformes 
// à la déclaration !
Voir Can I create an array whose component type is a concrete parameterized type?
Avatar de Yomhgui Yomhgui - Membre régulier http://www.developpez.com
le 21/08/2009 à 10:25
En même temps tu te voyais avec une API "Collections 2" incompatible avec l'API de Collections actuelles ???

Aucun doutes, ça aurait eu un côté difficile à faire avaler... (quoique je note quand même que tous les éditeurs ne s'embarrassent pas toujours avec ce genre de considérations).

Bon, après quelques réflexions sur le sujet, je ne suis même pas certain que le seule problématique de compatibilité ascendante soit en cause. Il me semble que, quand on crée une classe C paramétrée : C<T>, le comportement du compilateur est assez différent de ce qui se passe en C++ où, dans le code compilé, on trouve autant de classes différentes qu'il y a d'utilisation de la classe paramétrée avec des paramètres différents (si mes lointain souvenir du C++ sont exacts).

Dans le byte code Java, il ne doit y avoir qu'une seule définition de classe qui ne garde pas de trace du fait qu'elle possède un paramètre. Ce qui doit faire que, compatibilité ascendante où pas, on ne peut connaitre dans la classe compilée que la borne supérieure du paramètre déclaré dans le source.

Je me trompe ?

D'ailleurs, j'ai fait un petit essai simple, pas avec des classes mais avec des méthodes paramétrées:

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
private static <T> T get(final Class<T> c) { 
    try { 
        return c.newInstance(); 
    } catch (final Exception e) { 
        return null; 
    } 
} 
 
public static void main(final String[] args) { 
    final String s = get(String.class); 
    final Float f = get(Float.class); 
}
Le byte code du main donne:

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  // access flags 9 
  public static main([Ljava/lang/String;)V 
   L0 
    LINENUMBER 61 L0 
    LDC Ljava/lang/String;.class 
    INVOKESTATIC p/C.get(Ljava/lang/Class;)Ljava/lang/Object; 
    CHECKCAST java/lang/String 
    POP 
   L1 
    LINENUMBER 62 L1 
    LDC Ljava/lang/Float;.class 
    INVOKESTATIC p/C.get(Ljava/lang/Class;)Ljava/lang/Object; 
    CHECKCAST java/lang/Float 
    POP 
   L2 
    LINENUMBER 63 L2 
    RETURN 
   L3 
    LOCALVARIABLE args [Ljava/lang/String; L0 L3 0 
    MAXSTACK = 1 
    MAXLOCALS = 1
Et il n'y a bien qu'un seule définition de méthode get(Class), pas deux méthodes dont les noms auraient été décorés en get@String(...) : String et get@Float(...) : Float par exemple. C'est bien l'érésure du paramètre qui à cette conséquence. Les CHECKCAST ne sont pas prêts de disparaitre du byte code...
Avatar de adiGuba adiGuba - Expert éminent sénior http://www.developpez.com
le 21/08/2009 à 11:03
Citation Envoyé par Yomhgui  Voir le message
Aucun doutes, ça aurait eu un côté difficile à faire avaler... (quoique je note quand même que tous les éditeurs ne s'embarrassent pas toujours avec ce genre de considérations).

C'est ce qui a été fait dans C# 2.0, mais le langage était bien plus jeune et cela impactait bien moins de programme et de librairie que cela n'aurait été le cas en Java...

Citation Envoyé par Yomhgui  Voir le message
Bon, après quelques réflexions sur le sujet, je ne suis même pas certain que le seule problématique de compatibilité ascendante soit en cause. Il me semble que, quand on crée une classe C paramétrée : C<T>, le comportement du compilateur est assez différent de ce qui se passe en C++ où, dans le code compilé, on trouve autant de classes différentes qu'il y a d'utilisation de la classe paramétrée avec des paramètres différents (si mes lointain souvenir du C++ sont exacts).

En effet en C++ chaque utilisation avec un type différent génère une duplication de code. C'est comme si on faisait des copier-coller, sauf que c'est bien plus pratique car c'est le compilateur qui s'en charge (c'est pour cela que les codes utilisant les templates DOIVENT se trouver dans le .h).

Sauf erreur il y a un mécanisme similaire dans C#...

Citation Envoyé par Yomhgui  Voir le message
Dans le byte code Java, il ne doit y avoir qu'une seule définition de classe qui ne garde pas de trace du fait qu'elle possède un paramètre. Ce qui doit faire que, compatibilité ascendante où pas, on ne peut connaitre dans la classe compilée que la borne supérieure du paramètre déclaré dans le source.

Je me trompe ?

Non c'est exactement cela : à l'exécution tu ne peux savoir que ce qui est indiqué dans le code source de la classe, mais tu ne peux pas connaitre le type paramétré utilisé par chaque instance.

A noter que cette approche n'a pas forcément que des désavantages (le coût des casts étant relativement faible), puisqu'il permet de gérer facilement la covariance/contra-variance sur les types paramétrés.

a++
Avatar de Benouze Benouze - Membre averti http://www.developpez.com
le 17/09/2009 à 13:08
[Edit Ricky81](suite à fusion, ne blamez pas l'auteur ;-) [/Edit]

Bonjour,

Ci-après un billet de Stephan Schmidt tiré de son blog.

Vos opinions ?

Many companies and developers move away from Java to new languages: Ruby, Python, Groovy, Erlang, Scala. You might be trapped with Java.

Even if you’ve trapped, you can change your programming style and reap some of the benefits of those new languages. In the last 15 years Java programming style has changed significantly:

1. Final is your new love: More and more Java developers in the teams I’ve worked with, started to use final. Making variables final prevents changing those values. Most often, if you reasign a value, it’s a bug or you should use a new variable. Final prevents bugs and makes code easier to read and understand. Make everything immutable.

Code : Sélectionner tout
      final Foo bar = new Foo();
I’ve written more about this topic in “All variables in Java must be final”.

2. No setters. Many Java developers automatically – sometimes with the evil help of an IDE – write setters for all fields in their classes. You should not use setters. Think about each setter you want to write, are they really necessary for your fields? Better create new copies of your objects if values change. And try to write code without getters either. Tell, don’t ask tells you more about the concept.

3. Do not use loops for list operations. Learning from functional languages, looping isn’t the best way to work on collections. Suppose we want to filter a list of persons to those who can drink beer. The loop versions looks like:

Code : Sélectionner tout
1
2
3
4
5
6
      List<Person> beerDrinkers = new ArrayList<Person>(); 
      for (Person p: persons) { 
          if (p.getAge() > 16) { 
      	    beerDrinkers.add(p); 
          } 
      }
This can – even in Java – be rewritten to a more a functional programming style. For example using Google collections filter:

Code : Sélectionner tout
1
2
3
4
5
6
7
      Predicate<HasAge> canDrinkBeer = new Predicate<HasAge>() { 
          public boolean apply(HasAge hasAge) { 
              return hasAge.getAge() > 16; 
          } 
      }; 
 
      List<Person> beerDrinkers = filter(persons, canDrinkBeer);
As remarked by Dave Jarvis, I should have dropped the getter, and he’s right ;-)

Code : Sélectionner tout
1
2
3
4
5
      Predicate canDrinkBeer = new Predicate() { 
           public boolean apply(HasAge hasAge) { 
               return hasAge.isOlderThan( 16 ); 
          } 
      };
which would lead to better code down the road:

Code : Sélectionner tout
1
2
3
4
5
      Predicate canDrinkBeer = new Predicate() { 
          public boolean apply( HasAge hasAge, HasAge otherAge ) { 
              return hasAge.isOlderThan( otherAge ); 
          } 
      }
The predicate version is slightly larger, but consists of two parts. Each one is easier to understand. While the loop version gets unreadable fast with new requirements, the functional version can easily be combined,

Code : Sélectionner tout
      List beerDrinkers = filter(filter(persons, canDrinkBeer), isMale);
More on this at the Metaphysical Developer.

4. Use one liners: Write concise code. Java is a noisy language. But don’t make it noiser as it needs to be. Instead of

Code : Sélectionner tout
1
2
3
4
5
      public int add(int a, int b) 
      { 
        int result = a + b; 
        return result; 
      }
write
Code : Sélectionner tout
1
2
 
      public int add(int a, int b) { return a + b; }
IDEA and possibly other IDEs can keep oneliners formatted as oneliners, if you tell them so.

5. Use many, many objects with many interfaces. Domain driven design currently makes a big splash. A class should be splitted into many roles, implementing many interfaces. This way the class is reusable.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
      public class Person implements Nameable, Hireable, LivesAt { 
         ... 
      } 
 
      public interface Nameable { 
        String getName(); 
      } 
 
      Methods should be written to only work on roles, not classes. This way methods can work on more objects. Another benefit is lower coupling. 
 
      public void print(Nameable name) { 
        System.out.println(name.getName()); 
      }
I’ve written more about that in “Never, never, never use String in Java “.

6. Use Erlang-Style Concurrency. The Java concurrency primitives like locks and synchronized have been proven to be too low level and often to hard to use. There are better ways to write concurrent code. Erlang Style concurrency is one of them – in Java there are many ways to achive this in Java – I’ve written about them here. Newer ones are Akka and Actorom. You can also use Join/Fork or the myriad of data structures in java.util.concurrent.

7. Use Fluent Interfaces. Fluent interfaces can make your code much more readable and shorter. A very good example is the MapMaker API in Google Collections:

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
       ConcurrentMap graphs = new MapMaker() 
             .concurrencyLevel(32) 
             .softKeys() 
             .weakValues() 
             .expiration(30, TimeUnit.MINUTES) 
             .makeComputingMap( 
                 new Function() { 
                   public Graph apply(Key key) { 
                     return createExpensiveGraph(key); 
                   } 
                 });
8. Data Transfer Objects without setters and getters. If you have simple data holders or data transfer objects, don’t write boiler plate code with getters and setters. I know this is heresy in Java, but just use public fields. Instead of

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
      public class Point { 
         private int x; 
         private int y; 
 
         pulic Point(int x, int y) { 
            this.x = x; 
            this.y = y; 
         } 
         public int setX(int newX) { 
            x = newX; 
         } 
         public int getX() { 
           return x; 
         } 
         ... 
      } 
      ... 
      int x = p.getX(); 
      int y= p.getY();
write

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
      public class Point { 
         public final int x; 
         public final int y; 
 
         pulic Point(int x, int y) { 
            this.x = x; 
            this.y = y; 
         } 
      } 
      ... 
      int x = p.x; 
      int y = p.y;
Refactoring is easy, should you need it. If you do not control all the code and it’s usage, you might need to be more careful though.

This tips lead to better Java code. Try them in your next Java class. What do you think? I’d like to hear how your Java coding style has changed in the last 10 years.

Update: Some thoughts to Cedrics thoughts.

As the author of the post, your thoughts are appreciated, some of mine:

“Besides, it’s convenient to be able to mutate object if you want to use pools.”

No setters doesn’t mean you can’t mutate objects, it’s just that plain setters are not object oriented thinking. How about stop() vs. setStop(true);

“I think the first example is more readable than the second one that uses Predicates.”

Avatar de nouknouk nouknouk - Modérateur http://www.developpez.com
le 17/09/2009 à 13:56
Mouais,

il y a quand même quelques points avec lesquels je ne suis pas d'accord du tout. En fait tout dépend du projet (voire de la partie du projet) sur lequel on bosse et les priorités qu'on se donne (flexibilité, réutilisabilité, performance, clarté du code, ...).

Balancer des règles sans préciser le contexte, laissant penser qu'une règle est toujours valable dans tous les cas, est amha une erreur courante du niveau du développeur moyen à la recherche de LA réponse universelle. En développement comme dans beaucoup d'autres domaines, "choisir, c'est renoncer" comme dirait l'autre

Comme dans la vraie vie, la réalité des choses dépend fortement du contexte et des contraintes inhérentes au projet. Rien n'est jamais tout blanc ou tout noir. Quelques exemples:

- "Final is your new love": j'expériemente acutellement l'abus de ce genre de pratique dans un framework Java (pulpcore) où l'immense majorité des méthodes sont déclarées 'final'. Le résultat est que tant qu'on reste "dans les clous" de l'utilisation prévue par le concepteur, tout va bien. Mais dès qu'on veut faire quelque chose d'un peu plus spécifique, on se retrouve très vite complètement enfermé dans un carcan rigide, l'opposé même de la notion de framework. En résumé, autant ce genre de pratique peut-être bénéfique (notamment pour déceler les bugs dès l'écriture du code), autant dans le cas d'un code destiné à être réutilisé et/ou adapté (framework, librairie plus ou moins générique) ça devient vite une contrainte supplémentaire totalement inutile.

- "Do not use loops for list operations": bien que la solution proposée soit plus "élégante", la compacité et l'approche "classique" du code dans le premier exemple rend la relecture du code bien plus 'compréhensible' amha.

- "No setters" / "Data Transfer Objects without setters and getters": pas toujours: il est parfois extrêmement utile de pouvoir intercepter les appels aux getters/setters pour du debug, la POA, ou pour une classe dérivée qui veut adapter la valeur sotckée/retournée.

Tout ça pour dire que, quelles que soient les règles édictées, elle ne peuvent jamais être universelles ; Et même si elles me paraissent toutes plus ou moins pertinentes dans un contexte donné, amha son billet aurait beaucoup gagné à présenter pour chacune de ses règles leurs propres limites.
Avatar de adiGuba adiGuba - Expert éminent sénior http://www.developpez.com
le 17/09/2009 à 14:02
Salut,

Personnellement je trouvais que tout cela s'adapte mal au cas général, et que c'était donc franchement abusé de présenter cela comme de nouvelles règles de programmation

a++
Offres d'emploi IT
Expert technique sur riskone
Societe Generale - Ile de France - Ile de France
Architecte de données h/f
Societe Generale - Ile de France - Ile de France
Développeur symfony2 - médias
Omnilog - Ile de France - Neuilly-sur-Seine (92200)

Voir plus d'offres Voir la carte des offres IT
Responsables bénévoles de la rubrique Java : Mickael Baron - Robin56 -