Domanda

codice altamente ripetitivo è generalmente una brutta cosa, e ci sono modelli di progettazione che possono aiutare a ridurre al minimo questo. Tuttavia, a volte è semplicemente inevitabile a causa dei vincoli del linguaggio stesso. Prendiamo il seguente esempio da 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;
}

Il codice sopra appare nel codice sorgente 8 volte, con pochissime variazioni nella firma documentazione / metodo ma esattamente lo stesso corpo del metodo , uno per ciascuna delle radici tipi array int[], short[], char[], byte[], boolean[], double[], float[] e Object[].

Io credo che, se non si ricorre a riflessione (che è un argomento completamente diverso in sé), questa ripetizione è inevitabile. Capisco che in una classe di utilità, come ad alta concentrazione di codice Java ripetitivo è altamente atipico, ma anche con la best practice, ripetizione accade ! Refactoring non sempre funziona perché non è sempre possibile (il caso evidente è quando la ripetizione è nella documentazione).

Ovviamente il mantenimento di questo codice sorgente è un incubo. Un lieve errore di battitura nella documentazione, o di un piccolo bug nell'implementazione, è moltiplicato per tuttavia molte ripetizioni è stata fatta. Infatti, il miglior esempio accade per coinvolgere questa classe esatta:

Google Blog Research - Extra, Extra - Read All About It: Quasi tutte le ricerche binari e Mergesorts sono rotti (da Joshua Bloch, Software Engineer)

Il bug è sorprendentemente sottile, uno, che si verificano in quello che molti pensavano di essere solo un algoritmo semplice e lineare.

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

La linea di cui sopra appare 11 volte nel codice sorgente

Quindi le mie domande sono le seguenti:

  • Come sono questi tipi di codice ripetitivo Java / documentazione gestite in pratica? Come sono elaborati, mantenuti, e testato?
    • si avvia con "l'originale", e renderlo il più maturo possibile, e quindi copiare e incollare, se necessario, e la speranza non hai fatto un errore?
    • E se avete fatto fare un errore in originale, poi basta fissarlo ovunque, a meno che non sei a tuo agio con l'eliminazione delle copie e ripetendo l'intero processo di replica?
    • E l'applicazione di questo stesso processo per il codice di prova così?
  • vantaggio sarebbe Java da una sorta di uso limitato codice sorgente di pre-elaborazione per questo genere di cose?
    • Forse Sun ha il proprio preprocessore per aiutare a scrivere, a mantenere, documentare e testare questo tipo di codice della libreria ripetitivo?

Un commento ha richiesto un altro esempio, così ho tirato questo uno da Google Collezioni: com.google.common.base.Predicates linee 276-310 (AndPredicate) vs linee 312-346 (OrPredicate).

La sorgente di queste due classi sono identici, eccetto:

  • AndPredicate vs OrPredicate (ogni compare 5 volte nella sua classe)
  • "And(" vs Or(" (nei rispettivi metodi toString())
  • #and vs #or (nei commenti @see Javadoc)
  • true vs false (in apply; ! può essere riscritta dalla espressione)
  • -1 /* all bits on */ vs 0 /* all bits off */ in hashCode()
  • &= vs |= in hashCode()
È stato utile?

Soluzione

Per le persone che hanno bisogno di prestazioni assolutamente, boxe e unboxing e collezioni generified e quant'altro sono grandi no-no.

Lo stesso problema si verifica in performance computing in cui è necessario lo stesso complesso di lavorare sia per il float e double (diciamo un po 'del metodo illustrato in di Goldberd " Ciò che ogni scienziato informatico dovrebbe conoscere i numeri in virgola mobile " di carta).

C'è un motivo per cui Trove 's TIntIntHashMap corre cerchi intorno HashMap<Integer,Integer> di Java quando si lavora con una simile quantità di dati .

Ora, come sono il codice sorgente della collezione Trove scritto?

Utilizzando il codice sorgente di strumentazione ovviamente:)

Ci sono diverse librerie Java per prestazioni più elevate (molto più elevato rispetto a quelli di default Java) che utilizzano generatori di codice per creare il codice sorgente ripetuta.

Sappiamo tutti che "il codice sorgente di strumentazione" è il male e che la generazione del codice è una schifezza, ma comunque è così che le persone che sanno davvero cosa stanno facendo (cioè il tipo di persone che scrivono cose come Trove) fanno :)

Per quello che vale generiamo il codice sorgente che contiene grandi avvertimenti come:

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

Altri suggerimenti

Se è assolutamente necessario duplicare il codice, seguire i grandi esempi che hai dato e raggruppare tutti che il codice in un posto dove è facile da trovare e correggere quando si deve fare un cambiamento. Documentare la duplicazione e, cosa ancora più importante, la ragione per per la duplicazione in modo che tutti coloro che vengono dopo che si è a conoscenza di entrambe le cose.

Wikipedia Non Repeat Yourself (DRY) o duplicazione è il Male ( DIE)

In alcuni contesti, lo sforzo richiesto per far rispettare la filosofia secco può essere maggiore lo sforzo di mantenere copie separate dei dati. In altri contesti, le informazioni duplicate è immutabile o tenuto sotto un controllo abbastanza stretto per fare DRY non necessaria.

Non esiste probabilmente alcuna risposta o di una tecnica per evitare problemi del genere.

Anche i pantaloni fantasia linguaggi come Haskell hanno codice ripetitivo ( vedere il mio post su Haskell e serializzazione )

Sembra che ci sono tre scelte a questo problema:

  1. Usa riflessione e perdere prestazioni
  2. Utilizzo di pre-elaborazione, come modello di Haskell o Caml4p equivalente per la lingua e vivere con cattiveria
  3. o il mio uso personale preferito macro se la vostra lingua lo supporta (schema, e lisp)

io considero le macro diverse da pre-elaborazione in quanto le macro sono di solito nella stessa lingua che l'obiettivo è dove come pre-elaborazione è una lingua diversa.

Credo che le macro Lisp / regime sarebbe risolvere molti di questi problemi.

I ottenere che Sun ha a documentare come questo per il codice della libreria Java SE e forse altre biblioteche scrittori 3rd party fare pure.

Tuttavia, penso che sia uno spreco assoluto di copiare e incollare la documentazione durante un file come questo in codice che viene utilizzato solo in casa. So che molte persone non saranno d'accordo perché renderà loro in casa JavaDocs sembrano meno pulita. Tuttavia, il compromesso è che si rende loro codice più pulito, che, a mio parere, è più importante.

Java i tipi primitivi si vite, soprattutto quando si tratta di array. Se stai chiedendo esplicitamente di codice che coinvolge tipi primitivi, allora direi solo cercare di evitarli. Il metodo Object [] è sufficiente se si utilizzano i tipi di scatola.

In generale, è necessario un sacco di test di unità e non c'è davvero niente altro da fare, altro che ricorrere alla riflessione. Come hai detto tu, è un altro soggetto del tutto, ma non essere troppo paura di riflessione. Scrivere il codice più secco è possibile prima, poi il profilo it e determinare se il calo di prestazioni riflessione è davvero male abbastanza da giustificare la scrittura fuori e la manutenzione del codice aggiuntivo.

Si potrebbe utilizzare un generatore di codice per costruire varianti del codice utilizzando un modello. In tal caso, la fonte di Java è il prodotto del generatore e il codice vero e proprio è il modello.

Dati due frammenti di codice che si sostiene essere simile, la maggior parte delle lingue hanno limitato strutture per la costruzione di astrazioni che unificano i frammenti di codice in un monolite. Per astratto quando la lingua non può farlo, dovete fare un passo fuori della lingua: - {

Il più generale meccanismo "astrazione" è un processore completo macro che può applicarsi calcoli arbitrari al "corpo macro" mentre istanziandola (si pensi post o il sistema di stringa-riscrittura , che è in grado di Turing). M4 e GPM sono esempi per eccellenza. Il preprocessore C non è uno di questi.

Se si dispone di un processore ad esempio macro, è possibile costruire un "astrazione" come una macro, ed eseguire il processore macro sul testo di partenza "astratto" per produrre il codice sorgente si compila e si esegue.

È inoltre possibile utilizzare le versioni più limitate delle idee, spesso chiamato "generatori di codice". Questi non sono di solito Turing in grado, ma in molti casi funzionano abbastanza bene. Dipende da quanto sofisticato il vostro "macro di istanze" ha bisogno di essere. (La ragione per cui le persone sono innamorato con il C ++ meccanismo di template è THS, nonostante la sua bruttezza, è è di Turing compiti di generazione di codice davvero brutti, ma sorprendenti capaci e così la gente può fare con esso). Un'altra risposta qui cita Trove, che è apparantly nella categoria più limitata, ma ancora molto utile.

processori macro Davvero generali (come M4) manipolare il proprio testo; che li rende potenti, ma non gestiscono la struttura del linguaggio di programmazione ben, ed è davvero imbarazzante per scrivere un generaor in un tale processore mcaro che può non solo produrre codice, ma ottimizzare il risultato generato. La maggior parte dei generatori di codice che incontro sono "la spina questa stringa in questo modello stringa" e quindi non può fare qualsiasi ottimizzazione di un risultato generato. Se si desidera che la generazione di codice arbitrario e ad alte prestazioni per l'avvio, è necessario qualcosa che sia in grado di Turing, ma capisce la struttura del codice generato in modo che possa facilmente manipolare (ad esempio, l'ottimizzazione) esso).

Un tale strumento si chiama un sistema di trasformazione . Tale strumento analizza il testo sorgente come un compilatore fa, e quindi porta analisi / trasformazioni su di esso per ottenere un effetto desiderato. Se si può mettere i marcatori nel testo sorgente del programma che dirigono lo strumento programma di transformaiton cosa fare (ad esempio, commenti o le annotazioni in linguaggi che li hanno strutturato), quindi è possibile utilizzarlo per effettuare tale esemplificazione astrazione, la generazione di codice, e / o di ottimizzazione del codice. (Suggerimento Uno del manifesto di agganciare al compilatore Java è una variante di questa idea). Utilizzando un sistema generale puprose trasformazione (come DMS Software Reengineering Tookit significa che è possibile fare questo per sostanzialmente tutte le lingue.

Un sacco di questo tipo di ripetizione può ora essere evitato grazie a farmaci generici. Sono una manna dal cielo quando si scrive lo stesso codice in cui cambiano solo i tipi.

Purtroppo però, penso che gli array generici non sono ancora molto ben supportati. Per il momento, almeno, utilizzare contenitori che consentono di usufruire di farmaci generici. Il polimorfismo è anche uno strumento utile per ridurre questo tipo di duplicazione del codice.

Per rispondere alla tua domanda su come gestire il codice che deve essere assolutamente duplicato ... Tag ogni istanza con i commenti facili da trovare. Ci sono alcuni preprocessore Java là fuori, che aggiungono le macro stile C. Credo di ricordare NetBeans averne uno.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top