Question

Code très répétitif est généralement une mauvaise chose, et il y a des modèles de conception qui peuvent aider à minimiser cela. Cependant, il est parfois tout simplement inévitable en raison des contraintes de la langue elle-même. Prenons l'exemple suivant 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 dans le code source 8 fois, avec très peu de variation dans la signature des documents / méthode, mais exactement le même corps de la méthode , un pour chacun des types de tableau racine int[], short[], char[], byte[], boolean[], double[], float[] et Object[].

Je crois que si l'on recourt à 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 d'utilité, telle concentration de code Java répétitif est très atypique, mais même avec les meilleures pratiques, répétition ne se passe ! Refactoring ne fonctionne pas toujours parce qu'il est pas toujours possible (le cas évident est quand la répétition est dans la documentation).

Il est évident que le maintien de ce code source est un cauchemar. Une légère faute de frappe dans la documentation, ou un bug mineur dans la mise en œuvre, est multiplié par cependant de nombreuses répétitions ont été faites. En fait, le meilleur exemple arrive à impliquer cette classe exacte:

Google Blog recherche - Extra, extra - Lire All About It: Presque toutes les recherches binaires et Mergesorts sont cassés (par Joshua Bloch, ingénieur logiciel)

Le bug est un étonnamment subtile, se produisant dans ce que beaucoup pensaient être juste 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

Alors mes questions sont:

  • Comment ces types de code Java répétitif / documentation traitées dans la pratique? Comment sont-ils développés, maintenus et testés?
    • Avez-vous commencer par « l'original », et le rendre aussi mature que possible, puis copier et coller au besoin et espérons que vous ne l'avez pas fait une erreur?
    • Et si vous avez fait une erreur dans l'original, puis juste fixer partout, à moins que vous êtes à l'aise avec la suppression des copies et de répéter l'ensemble du processus de réplication?
    • Et vous appliquez ce même processus pour le code de test ainsi?
  • Bénéficierait Java d'une sorte de pré-traitement de code source à usage limité pour ce genre de chose?
    • Peut-être Sun a leur propre préprocesseur pour aider à écrire, maintenir, documenter et tester ce genre de code de la bibliothèque répétitive?

Un commentaire a demandé un autre exemple, je tire celui de Google Collections: les lignes de com.google.common.base.Predicates 276-310 (AndPredicate) vs lignes 312-346 (OrPredicate).

La source de ces deux classes sont identiques, sauf pour:

  • AndPredicate vs OrPredicate (chacun apparaît 5 fois dans sa catégorie)
  • "And(" vs Or(" (dans les procédés de toString() respectifs)
  • #and vs #or (dans les commentaires Javadoc @see)
  • true vs false (en apply; ! peut être réécrite de l'expression)
  • -1 /* all bits on */ vs 0 /* all bits off */ dans hashCode()
  • &= vs |= dans hashCode()
Était-ce utile?

La solution

Pour les personnes qui ont besoin de performances tout à fait, la boxe et collections unboxing et generified et ainsi de suite sont grands non-non.

Le même problème se produit dans le calcul de la performance où vous avez besoin du même complexe à travailler à la fois pour float et double (par exemple une partie de la méthode indiquée dans Goldberd de « Ce que tout informaticien doit savoir sur les nombres à virgule flottante " papier).

Il y a une raison pour laquelle Trove TIntIntHashMap de exécute des cercles autour de HashMap<Integer,Integer> Java lorsque vous travaillez avec une quantité similaire de données .

Maintenant, comment sont le code source de collection Trove écrit?

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

Il existe plusieurs bibliothèques Java pour des performances plus élevées (beaucoup plus élevé que ceux par défaut Java) qui utilisent des générateurs de code pour créer le code source répétée.

Nous savons tous que « l'instrumentation de code source » est mal et que la génération de code est de la merde, mais c'est comment les gens qui savent vraiment ce qu'ils font (à savoir le genre de personnes qui écrivent des choses comme Trove) font :)

Pour ce qu'il vaut la peine que nous générons le code source qui contient de grands avertissements comme:

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

Autres conseils

Si vous devez absolument dupliquer du code, suivez les grands exemples que vous avez donnés et regrouper tous ce code dans un endroit où il est facile de trouver et de corriger lorsque vous devez faire un changement. Documenter la duplication et, plus important encore, la raison de la duplication afin que tous ceux qui viennent après vous est au courant des deux.

De Wikipédia Ne vous répétez (SEC) ou Duplication est mal ( DIE)

Dans certains contextes, l'effort nécessaire pour faire appliquer la philosophie SEC peut être supérieure à l'effort de conserver des copies séparées des données. Dans certains autres contextes, des informations dupliquées est immuable ou maintenue sous un contrôle assez serré pour faire dessèche pas nécessaire.

Il n'y a probablement pas de réponse ou d'une technique pour éviter des problèmes comme ça.

langues pantalons Même fantaisie comme Haskell ont un code répétitif ( voir mon post sur haskell et sérialisation )

Il semble qu'il ya trois choix à ce problème:

  1. Utiliser la réflexion et de perdre la performance
  2. Utiliser comme modèle pré-traitement Haskell ou Caml4p équivalent pour votre langue et vivre avec méchancetés
  3. Ou mes macros usage personnel favori si votre langue supporte (schéma et Lisp)

Je considère que les macros différentes que prétraiter parce que les macros sont généralement dans la même langue que la cible est là comme pré-traitement est une autre langue.

Je pense que Lisp / macros Scheme résoudrait bon nombre de ces problèmes.

J'obtenir que Sun doit documenter comme celui-ci pour le code de la bibliothèque Java SE et peut-être d'autres écrivains de la bibliothèque 3ème partie faire aussi bien.

Cependant, je pense qu'il est un gaspillage éhonté de copier et de coller la documentation tout au long d'un fichier comme celui-ci dans le code qui est utilisé uniquement dans la maison. Je sais que beaucoup de gens seront en désaccord, car il fera leur maison à JavaDocs regarder moins propre. Cependant, le commerce est ce qui rend leur est un code plus propre qui, à mon avis, est plus important.

Java types primitifs vous vis, en particulier en ce qui concerne les tableaux. Si vous spécifiquement poser des questions sur le code impliquant des types primitifs, alors je dirais juste essayer de les éviter. L'objet [méthode] est suffisante si vous utilisez les types boxed.

En général, vous avez besoin d'un bon nombre de tests unitaires et il n'y a vraiment rien d'autre à faire, autre que le recours à la réflexion. Comme vous l'avez dit, il est tout à fait un autre sujet, mais ne soyez pas trop peur de la réflexion. Ecrire le code plus sec vous pouvez d'abord, le profil et puis déterminer si la baisse de performance de réflexion est vraiment assez mauvais pour justifier l'écriture sur le maintien et le code supplémentaire.

Vous pouvez utiliser un générateur de code pour construire des variations du code à l'aide d'un modèle. Dans ce cas, la source de Java est un produit du générateur et le code réel est le modèle.

Étant donné deux fragments de code qui sont jugées similaires, la plupart des langues ont des moyens limités pour construire des abstractions qui unifient les fragments de code dans un monolithe. Pour faire abstraction lorsque votre langue ne peut le faire, vous devez sortir la langue: - {

Le mécanisme le plus général « abstraction » est un processeur macro complet qui peut appliquer des calculs arbitraires au « corps macro », tandis que instancier (pensez Poste ou système de chaîne de réécriture , qui est capable de Turing). M4 et sont des exemples gpm par excellence. Le préprocesseur C est pas un de ceux-ci.

Si vous avez un tel processeur macro, vous pouvez construire une « abstraction » comme une macro, et exécutez le processeur macro sur votre texte source « abstrait » pour produire le code source vous compilez et exécutez.

Vous pouvez également utiliser des versions plus limitées des idées, souvent appelés « générateurs de code ». Ceux-ci ne sont généralement pas capables de Turing, mais dans de nombreux cas, ils fonctionnent assez bien. Cela dépend de votre degré de sophistication « instanciation macro » doit être. (Si les gens sont séduits par le mécanisme de modèle C de est ths malgré sa laideur, il est Turing capables et que les gens puissent faire des tâches vraiment de génération de code laid, mais étonnante avec elle). Une autre réponse ici mentionne Trove, qui est apparantly dans la catégorie plus limitée mais toujours très utile.

processeurs macro vraiment général (comme M4) manipuler le texte juste; qui les rend puissants, mais ils ne gèrent pas la structure du langage de programmation bien, et il est vraiment difficile à écrire un generaor dans un tel processeur mcaro qui ne peut produire que du code, mais d'optimiser le résultat généré. La plupart des générateurs de code que je rencontre sont « brancher cette chaîne dans ce modèle de chaîne » et ne peut donc faire aucune optimisation d'un résultat généré. Si vous voulez que la génération de code arbitraire et de haute performance pour démarrer, vous avez besoin quelque chose qui est capable, mais comprend Turing la structure du code généré de sorte qu'il peut manipuler facilement (par exemple, optimiser) il).

Un tel outil est appelé un . Un tel outil analyse le texte source comme un compilateur fait, et effectue ensuite des analyses / transformations sur elle pour obtenir un effet désiré. Si vous pouvez mettre des marqueurs dans le texte source de votre programme (par exemple, structuré des commentaires ou des annotations dans langauges qui les ont) diriger l'outil transformaiton de programme ce qu'il faut faire, alors vous pouvez l'utiliser pour effectuer cette instanciation d'abstraction, génération de code, et / ou d'optimisation de code. (Une suggestion d'affiche de l'accrochage dans le compilateur Java est une variante de cette idée). L'utilisation d'un système de transformation de puprose générale (comme DMS Software Reengineering Tookit signifie que vous pouvez faire pour essentiellement toutes les langues.

Beaucoup de ce genre de répétition peut maintenant être évité grâce aux médicaments génériques. Ils sont un don du ciel lors de l'écriture du même code où seuls les types changent.

Malheureusement, cependant, je pense que les tableaux génériques ne sont toujours pas très bien pris en charge. Pour l'instant au moins, utiliser des conteneurs qui vous permettent de profiter des génériques. Polymorphisme est aussi un outil utile pour réduire ce genre de duplication de code.

Pour répondre à votre question sur la façon de gérer le code qui doit absolument être dupliquée ... Tag chaque instance avec des commentaires facilement consultables. Il y a quelques java préprocesseurs là-bas, qui ajoutent des macros de style C. Je pense que je me souviens NetBeans avoir.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top