72 votes

Gestion du code et de la documentation hautement répétitifs en Java

Un code très répétitif est généralement une mauvaise chose, et il existe des modèles de conception qui peuvent aider à minimiser ce phénomène. Cependant, il est parfois tout simplement inévitable en raison des contraintes du langage lui-même. Prenons l'exemple suivant, tiré de java.util.Arrays :

/**
 * Assigns the specified long value to each element of the specified
 * range of the specified array of longs.  The range to be filled
 * extends from index <tt>fromIndex</tt>, inclusive, to index
 * <tt>toIndex</tt>, exclusive.  (If <tt>fromIndex==toIndex</tt>, the
 * range to be filled is empty.)
 *
 * @param a the array to be filled
 * @param fromIndex the index of the first element (inclusive) to be
 *        filled with the specified value
 * @param toIndex the index of the last element (exclusive) to be
 *        filled with the specified value
 * @param val the value to be stored in all elements of the array
 * @throws IllegalArgumentException if <tt>fromIndex &gt; toIndex</tt>
 * @throws ArrayIndexOutOfBoundsException if <tt>fromIndex &lt; 0</tt> or
 *         <tt>toIndex &gt; a.length</tt>
 */
public static void fill(long[] a, int fromIndex, int toIndex, long val) {
    rangeCheck(a.length, fromIndex, toIndex);
    for (int i=fromIndex; i<toIndex; i++)
        a[i] = val;
}

L'extrait ci-dessus apparaît 8 fois dans le code source, avec très peu de variation dans la documentation/signature de méthode mais exactement le même corps de méthode un pour chacun des types de tableaux Root. int[] , short[] , char[] , byte[] , boolean[] , double[] , float[] et Object[] .

Je crois qu'à moins de recourir à la réflexion (ce qui est un tout autre sujet en soi), cette répétition est inévitable. Je comprends qu'en tant que classe utilitaire, une telle concentration de code Java répétitif est très atypique, mais même avec les meilleures pratiques, la répétition se produit ! Le refactoring ne fonctionne pas toujours car ce n'est pas toujours possible (le cas évident est lorsque la répétition se trouve dans la documentation).

Il est évident que la maintenance de ce code source est un cauchemar. Une légère coquille dans la documentation, ou un bug mineur dans l'implémentation, est multiplié par le nombre de répétitions. En fait, le meilleur exemple se trouve être cette classe exacte :

Blogue de recherche Google - Extra, Extra - Lisez tout à ce sujet : Presque toutes les recherches binaires et les triages par fusion sont cassés (par Joshua Bloch, ingénieur logiciel)

Le bogue est étonnamment subtil et se produit dans ce que beaucoup pensaient être un algorithme simple et direct.

    // int mid =(low + high) / 2; // the bug
    int mid = (low + high) >>> 1; // the fix

La ligne ci-dessus apparaît 11 fois dans le code source !

Mes questions sont donc les suivantes :

  • Comment ces types de code/documentation Java répétitifs sont-ils traités dans la pratique ? Comment sont-ils développés, maintenus et testés ?
    • Faut-il commencer par "l'original", et le rendre aussi mature que possible, puis copier et coller si nécessaire et espérer ne pas avoir fait d'erreur ?
    • Et si vous avez fait une erreur dans l'original, il suffit de la corriger partout, à moins que vous ne soyez à l'aise avec la suppression des copies et la répétition de tout le processus de réplication ?
    • Et vous appliquez ce même processus pour le code de test également ?
  • Java bénéficierait-il d'une sorte de prétraitement du code source à usage limité pour ce genre de choses ?
    • Peut-être Sun a-t-il son propre préprocesseur pour aider à écrire, maintenir, documenter et tester ce genre de code de bibliothèque répétitif ?

Un commentaire demandait un autre exemple, j'ai donc tiré celui-ci de Google Collections : com.google.common.base.Predicates lignes 276-310 ( AndPredicate ) contre les lignes 312-346 ( OrPredicate ).

La source de ces deux classes est identique, à l'exception de :

  • AndPredicate vs OrPredicate (chacun apparaît 5 fois dans sa classe)
  • "And(" vs Or(" (dans leurs toString() méthodes)
  • #and vs #or (dans le @see Commentaires de la Javadoc)
  • true vs false (en apply ; ! peut être réécrit hors de l'expression)
  • -1 /* all bits on */ vs 0 /* all bits off */ sur hashCode()
  • &= vs |= sur hashCode()

11 votes

Il semble que vous soyez particulièrement préoccupé par les répétitions dues au code de manipulation des tableaux primitifs. Personnellement, j'évite ce genre de répétition (et j'encourage les autres à faire de même) en utilisant des collections génériques et l'autoboxing, en évitant les tableaux et les primitives sauf en cas de nécessité absolue. Avez-vous des exemples de cette répétition qui Ne le fais pas. impliquent des tableaux primitifs ?

2 votes

+1 pour ce bon article et netlib.bell-labs.com/cm/cs/pearls

0 votes

La répétitivité due à la fourniture de surcharges complètes n'est qu'un exemple. J'ai vu ce genre de répétition dans des scénarios de gestion de tableaux non surchargés et non primitifs également.

32voto

SyntaxT3rr0r Points 10771

Pour les personnes qui ont absolument besoin de performances, la mise en boîte, le déballage, les collections généralisées et autres sont de grands interdits.

Le même problème se pose dans le domaine du calcul des performances, où il faut que le même complexe fonctionne à la fois pour les flottants et les doubles (par exemple, certaines des méthodes présentées dans l'ouvrage de Goldberd. _" Ce que tout informaticien devrait savoir sur les nombres à virgule flottante "_ papier).

Il y a une raison pour laquelle Trove 's TIntIntHashMap fait le tour de l'outil Java HashMap<Integer,Integer> lorsqu'on travaille avec une quantité similaire de données.

Maintenant, comment le code source de la collection Trove est-il écrit ?

En utilisant l'instrumentation du code source, bien sûr :)

Il existe plusieurs bibliothèques Java plus performantes (bien plus que les bibliothèques Java par défaut) qui utilisent des générateurs de code pour créer le code source répété.

Nous savons tous que "l'instrumentation du code source" est un mal et que la génération de code est de la merde, mais c'est quand même comme ça que font les gens qui savent vraiment ce qu'ils font (c'est-à-dire le genre de personnes qui écrivent des trucs comme Trove) :)

Pour ce que ça vaut, nous générons du code source qui contient de gros avertissements comme :

/*
 * This .java source file has been auto-generated from the template xxxxx
 * 
 * DO NOT MODIFY THIS FILE FOR IT SHALL GET OVERWRITTEN
 * 
 */

0 votes

Pouvez-vous fournir plus de détails sur les générateurs de code qu'ils utilisent, etc. Je ne suis pas familier avec Trove.

1 votes

C'est expliqué dans la FAQ de Trove, en gros ils ont une cible Ant qui appelle un script qui fait la modification (si je me souviens bien) : trove4j.sourceforge.net/html/faq.html (Je suis dans l'informatique haute performance Java et j'ai vu cette technique utilisée plusieurs fois...) Nous l'utilisons ici, nous avons notre propre code propriétaire Java qui génère plus de code Java :)

1 votes

@polygenelubricants : btw Trove est un merveilleux remplacement pour l'API Java par défaut si vous avez besoin de travailler avec des primitives. Pour les collections régulières, il faut se tourner vers Javolution ou les collections Google, etc. Les collections Java par défaut sont vraiment très mauvaises à bien des égards. Elles fonctionnent pour des projets simples, mais elles montrent rapidement leurs limites dès que l'on commence à manipuler des quantités importantes de données.

16voto

Bill the Lizard Points 147311

Si vous devez absolument dupliquer du code, suivez les excellents exemples donnés et regroupez tout ce code en un seul endroit où il sera facile à trouver et à corriger lorsque vous devrez effectuer une modification. Documentez la duplication et, plus important encore, les modifications apportées au code. raison de la dup duplication afin que tous ceux qui viendront après vous soient conscients des deux.

1 votes

+1 Augmenter la longueur de la documentation dupliquée en documentant la duplication semble être une mauvaise idée à première vue, mais c'est vraiment bien pire d'avoir des éléments dupliqués qui doivent être modifiés et aucune documentation sur la duplication.

6voto

stacker Points 34209

De Wikipedia Ne vous répétez pas (DRY) ou La duplication est un mal (DIE)

Dans certains contextes, l'effort nécessaire pour appliquer la philosophie DRY peut être plus important que l'effort nécessaire pour maintenir des copies séparées des données. Dans d'autres contextes, les informations dupliquées sont immuables ou maintenues sous un contrôle suffisamment strict pour que le DRY ne soit pas nécessaire.

Il n'y a probablement pas de réponse ou de technique pour éviter ce genre de problèmes.

4voto

Adam Gent Points 15055

Même les langages fantaisistes comme Haskell ont du code répétitif ( voir mon billet sur haskell et la sérialisation )

Il semble qu'il y ait trois possibilités pour résoudre ce problème :

  1. Utiliser la réflexion et perdre la performance
  2. Utilisez un prétraitement comme l'équivalent de Template Haskell ou Caml4p pour votre langage et vivez avec la méchanceté.
  3. Ou mon préféré, les macros, si votre langage les supporte (scheme et lisp).

Je considère que les macros sont différentes du prétraitement parce que les macros sont généralement dans la même langue que la cible, alors que le prétraitement est une langue différente.

Je pense que les macros Lisp/Scheme résoudraient beaucoup de ces problèmes.

2voto

noah Points 9333

Les types primitifs de Java vous bousillent, surtout lorsqu'il s'agit de tableaux. Si vous demandez spécifiquement du code impliquant des types primitifs, alors je dirais qu'il faut essayer de les éviter. La méthode Object[] est suffisante si vous utilisez les types encadrés.

En général, vous avez besoin de beaucoup de tests unitaires et il n'y a pas vraiment autre chose à faire que de recourir à la réflexion. Comme vous l'avez dit, c'est un tout autre sujet, mais n'ayez pas trop peur de la réflexion. Écrivez d'abord le code le plus sec possible, puis établissez son profil et déterminez si l'impact sur les performances de la réflexion est suffisamment important pour justifier l'écriture et la maintenance de ce code supplémentaire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X