Frage

Hochweit wiederholender Code ist im Allgemeinen eine schlechte Sache, und es gibt Designmuster, die dazu beitragen können, dies zu minimieren. Manchmal ist es jedoch einfach unvermeidlich aufgrund der Einschränkungen der Sprache selbst. Nehmen Sie das folgende Beispiel von 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;
}

Das obige Ausschnitt erscheint 8 Mal im Quellcode, mit sehr geringen Unterschiede in der Dokumentations-/Methodensignatur, jedoch jedoch Genau die gleiche Methodekörper, einer für jeden der Wurzelarray -Typen int[], short[], char[], byte[], boolean[], double[], float[], und Object[].

Ich glaube, dass diese Wiederholung unvermeidlich ist, wenn man nicht auf Reflexion zurückgreift (was ein völlig anderes Thema an sich ist). Ich verstehe, dass eine so hohe Konzentration des sich wiederholenden Java -Codes als Versorgungsklasse sehr atypisch ist, aber selbst mit der besten Praxis, Wiederholung passiert! Refactoring funktioniert nicht immer, da es nicht immer möglich ist (der offensichtliche Fall ist, wenn sich die Wiederholung in der Dokumentation befindet).

Offensichtlich ist es ein Albtraum, diesen Quellcode zu pflegen. Ein leichter Tippfehler in der Dokumentation oder ein geringfügiger Fehler in der Implementierung wird durch so viele Wiederholungen multipliziert. In der Tat beinhaltet das beste Beispiel zufällig genau diese Klasse:

Google Research Blog - extra, extra - Lesen Sie alles darüber: Fast alle binären Suchanfragen und Mergesorts sind gebrochen (von Joshua Bloch, Software -Ingenieur)

Der Fehler ist überraschend subtil und kommt in dem auf, was viele als einfach und unkompliziert angesehen haben.

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

Die obige Zeile erscheint 11 Mal im Quellcode!

Also meine Fragen sind:

  • Wie werden diese Art von sich wiederholenden Java -Code/Dokumentation in der Praxis behandelt? Wie werden sie entwickelt, gewartet und getestet?
    • Beginnen Sie mit "dem Original" und machen es so reif wie möglich und kopieren und fügen Sie sie nach Bedarf ein und hoffen Sie, dass Sie keinen Fehler gemacht haben?
    • Und wenn Sie im Original einen Fehler gemacht haben, dann beheben Sie ihn einfach überall, es sei denn, Sie können die Kopien löschen und den gesamten Replikationsprozess wiederholen?
    • Und Sie wenden denselben Prozess auch für den Testcode an?
  • Würde Java von einer Art begrenzter Nutzungsquellcode-Vorverarbeitung für solche Dinge profitieren?
    • Vielleicht hat Sun ihren eigenen Präprozessor, um diese Art von sich wiederholenden Bibliothekscode zu schreiben, zu warten, zu dokumentieren und zu testen?

Ein Kommentar forderte ein weiteres Beispiel an, also habe ich diesen von Google Collections gezogen: com.google.common.base.Predicates Zeilen 276-310 (AndPredicate) vs Zeilen 312-346 (OrPredicate).

Die Quelle für diese beiden Klassen ist identisch, mit Ausnahme von:

  • AndPredicate vs OrPredicate (Jeder erscheint 5 Mal in seiner Klasse)
  • "And(" vs Or(" (in den jeweiligen toString() Methoden)
  • #and vs #or (in dem @see Javadoc Kommentare)
  • true vs false (in apply; ! kann aus dem Ausdruck umgeschrieben werden)
  • -1 /* all bits on */ vs 0 /* all bits off */ in hashCode()
  • &= vs |= in hashCode()
War es hilfreich?

Lösung

Für Personen, die absolut Leistung, Boxen und Unboxen und generierte Kollektionen benötigen, und so weiter sind große No-No's.

Das gleiche Problem tritt bei Performance Computing auf, bei dem Sie den gleichen Komplex benötigen, um sowohl für Float als auch für Doppel zu arbeiten (z. B. einige der in Goldberds gezeigten Methode "Was jeder Informatiker über Floating-Punkt-Zahlen wissen sollte" Papier).

Es gibt einen Grund warum Trove's TIntIntHashMap Läuft Kreise um Javas HashMap<Integer,Integer> Bei der Arbeit mit einer ähnlichen Datenmenge.

Wie werden die Quellcode der Trove Collection nun geschrieben?

Verwenden Sie natürlich Quellcode -Instrumentierung :)

Es gibt mehrere Java -Bibliotheken für eine höhere Leistung (viel höher als die Standard -Java), die Codegeneratoren verwenden, um den wiederholten Quellcode zu erstellen.

Wir alle wissen, dass "Quellcode -Instrumentierung" böse ist und dass die Codegenerierung Mist ist, aber so wissen Leute, die wirklich wissen, was sie tun (dh die Art von Menschen, die Dinge wie Trove schreiben), es tun :)

Für das, was es wert ist, generieren wir Quellcode, der große Warnungen enthält wie:

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

Andere Tipps

Wenn Sie den Code unbedingt duplizieren müssen, befolgen Sie die großartigen Beispiele, die Sie angegeben haben, und gruppieren Sie den gesamten Code an einem Ort, an dem es einfach ist, zu finden und zu beheben, wenn Sie eine Änderung vornehmen müssen. Dokumentieren Sie die Duplikation und vor allem die Grund für die Duplizierung Damit sich jeder, der nach dir kommt, beides kennt.

Aus Wikipedia Wiederhole dich nicht (trocken) oder Duplizierung ist böse (sterben)

In einigen Kontexten kann der Aufwand zur Durchsetzung der Trockenphilosophie größer sein als die Bemühungen, separate Kopien der Daten aufrechtzuerhalten. In einigen anderen Kontexten sind doppelte Informationen unveränderlich oder unter der Kontrolle fest genug, um trocken nicht erforderlich zu machen.

Es gibt wahrscheinlich keine Antwort oder Technik, um solche Probleme zu verhindern.

Sogar ausgefallene Hosensprachen wie Haskell haben sich wiederholten Code (Sehen Sie sich meinen Beitrag über Haskell und Serialisierung an)

Es scheint, dass dieses Problem drei Möglichkeiten gibt:

  1. Verwenden Sie Reflexion und verlieren Sie die Leistung
  2. Verwenden Sie die Vorverarbeitung wie Vorlage Haskell oder CAML4P -Äquivalent für Ihre Sprache und leben Sie mit Bosheit
  3. Oder mein persönlicher Favorit verwenden Makros, wenn Ihre Sprache es unterstützt (Schema und Lisp)

Ich betrachte die Makros anders als die Vorverarbeitung, da die Makros normalerweise in derselben Sprache sind, in der das Ziel als Vorverarbeitung eine andere Sprache ist.

Ich denke, Lisp/Scheme -Makros würden viele dieser Probleme lösen.

Ich verstehe, dass die Sonne so für den Java SE -Bibliothekscode dokumentieren muss, und vielleicht auch andere Autoren der Drittanbieter.

Ich denke jedoch, dass es eine völlige Verschwendung ist, Dokumentationen in einer solchen Datei wie dieser in Code zu kopieren und einzufügen, die nur im Haus verwendet wird. Ich weiß, dass viele Menschen nicht zustimmen werden, weil es ihr im Haus Javadocs weniger sauber aussehen lässt. Der Kompromiss ist jedoch, dass ihr Code sauberer wird, was meiner Meinung nach wichtiger ist.

Java Primitive Typen schrauben Sie, besonders wenn es um Arrays geht. Wenn Sie ausdrücklich nach Code fragen, das primitive Typen beinhaltet, würde ich sagen, dass Sie sie einfach vermeiden. Die Objekt [] -Methode ist ausreichend, wenn Sie die Boxtypen verwenden.

Im Allgemeinen brauchen Sie viele Einheiten -Tests und es gibt wirklich nichts anderes zu tun, außer auf Reflexion zurückzugreifen. Wie Sie sagten, ist es ein anderes Thema, aber keine zu viel Angst vor Reflexion. Schreiben Sie den trockensten Code, den Sie zuerst können, dann profilieren Sie ihn und stellen Sie fest, ob der Hit der Reflexionsleistung wirklich schlimm genug ist, um das Schreiben und Aufrechterhalten des zusätzlichen Code zu rechtfertigen.

Sie können einen Codegenerator verwenden, um Variationen des Codes mithilfe einer Vorlage zu konstruieren. In diesem Fall ist die Java -Quelle ein Produkt des Generators und der reale Code ist die Vorlage.

Angesichts von zwei Code -Fragmenten, von denen behauptet wird, dass sie ähnlich sind, verfügen die meisten Sprachen nur begrenzte Einrichtungen für die Konstruktion von Abstraktionen, die die Codefragmente in einen Monolithen vereinen. Um zu abstrakt, wenn Ihre Sprache dies nicht kann, müssen Sie die Sprache aussteigen:-{

Der allgemeinste "Abstraktions" -Mechanismus ist ein vollständiger Makroprozessor, der bei der Instanziation der "Makrokörper" willkürliche Berechnungen anwenden kann (denken Sie an Post- oder String-Wrewriting System, das fähig ist). M4 und Gpm sind typische Beispiele. Der C -Präprozessor ist nicht davon.

Wenn Sie über einen solchen Makroprozessor verfügen, können Sie eine "Abstraktion" als Makro erstellen und den Makroprozessor auf Ihrem "abstrahierten" Quelltext ausführen, um den tatsächlichen Quellcode zu erstellen, den Sie kompilieren und ausführen.

Sie können auch begrenzte Versionen der Ideen verwenden, die häufig als "Codegeneratoren" bezeichnet werden. Diese sind normalerweise nicht fähig, aber in vielen Fällen funktionieren sie gut genug. Es hängt davon ab, wie hoch entwickelt Ihre "Makro -Instanziierung" sein muss. (Der Grund, warum Menschen in den C ++ - Vorlagenmechanismus verliebt sind ist Turing fähig und so können die Menschen wirklich hässliche, aber erstaunliche Aufgaben der Codegenerierung damit erledigen). Eine andere Antwort hier erwähnt Trove, was offensichtlich in der begrenzteren, aber immer noch sehr nützlichen Kategorie liegt.

Wirklich allgemeine Makroprozessoren (wie M4) manipulieren nur Text; Das macht sie mächtig, aber sie bewältigen nicht mit der Struktur der Programmiersprache gut, und es ist wirklich unangenehm, einen Gattung in einem solchen Mcaro -Prozessor zu schreiben, der nicht nur Code erzeugen, sondern das generierte Ergebnis optimiert. Die meisten Codegeneratoren, auf die ich begegnet bin, sind "diese Zeichenfolge in diese String -Vorlage stecken" und kann daher keine Optimierung eines generierten Ergebniss durchführen. Wenn Sie die Erzeugung von willkürlichem Code und hoher Leistung erzeugen möchten, benötigen Sie etwas, das fähig ist, aber die Struktur des generierten Codes versteht, damit er es leicht manipulieren kann (z. B. optimieren)).

Ein solches Werkzeug wird als a genannt Programmtransformationssystem. Ein solches Tool analysiert den Quelltext wie ein Compiler und trägt dann Analysen/Transformationen darauf, um einen gewünschten Effekt zu erzielen. Wenn Sie Markierungen in den Quelltext Ihres Programms (z. B. strukturierte Kommentare oder Anmerkungen in Langaugens, die sie haben) einstellen können, die das Programmtransformiton -Tool anweisen, was zu tun ist, können Sie es verwenden, um eine solche Abstraktionsinstantiation, Code -Generierung und -erzeugung auszuführen und zu erzeugen. /oder Codeoptimierung. (Der Vorschlag eines Plakats, sich in den Java -Compiler zu beschäftigen, ist eine Variation dieser Idee). Verwenden eines allgemeinen Puprose -Transformationssystems (wie z. DMS -Software -Reengineering nahm sich auf bedeutet, dass Sie dies im Wesentlichen für jede Sprache tun können.

Viele dieser Art von Wiederholung können jetzt dank Generika vermieden werden. Sie sind ein Glücksfall, wenn Sie denselben Code schreiben, in dem sich nur die Typen ändern.

Leider denke ich, dass generische Arrays immer noch nicht sehr gut unterstützt werden. Verwenden Sie zumindest vorerst Container, mit denen Sie Generika nutzen können. Polymorphismus ist auch ein nützliches Instrument, um diese Art von Code -Duplikation zu verringern.

Um Ihre Frage zu beantworten, wie Sie mit Code umgehen müssen, der unbedingt dupliziert werden muss, markieren Sie jede Instanz mit leicht durchsuchbaren Kommentaren. Es gibt einige Java-Präprozessoren, die Makros im C-Stil hinzufügen. Ich glaube, ich erinnere mich, dass Netbeans einen hatte.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top