Sehr sich wiederholender Code ist im Allgemeinen eine schlechte Sache, und es gibt Entwurfsmuster, die helfen können, dies zu minimieren. Manchmal ist es jedoch aufgrund der Einschränkungen der Sprache selbst einfach unvermeidlich. Nehmen Sie das folgende Beispiel aus 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 > toIndex</tt>
* @throws ArrayIndexOutOfBoundsException if <tt>fromIndex < 0</tt> or
* <tt>toIndex > 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;
}
Die obige Schnipsel erscheint im Quellcode 8 mal, mit sehr wenig Änderung in der Dokumentation / Methodensignatur aber genau die gleiche Methode Körper , eine für jede der Wurzel - Array - 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 an sich ein völlig anderes Thema ist). Ich verstehe, dass als Dienstprogrammklasse eine derart hohe Konzentration an sich wiederholendem Java-Code höchst untypisch ist, aber selbst mit der besten Vorgehensweise kommt es zu Wiederholungen ! Refactoring funktioniert nicht immer, weil es nicht immer möglich ist (der offensichtliche Fall ist, wenn die Wiederholung in der Dokumentation enthalten ist).
Offensichtlich ist die Pflege dieses Quellcodes ein Albtraum. Ein kleiner Tippfehler in der Dokumentation oder ein kleiner Fehler in der Implementierung wird mit der Anzahl der Wiederholungen multipliziert. Tatsächlich handelt es sich bei dem besten Beispiel um genau diese Klasse:
Der Fehler ist überraschend subtil und tritt in dem auf, was viele für einen einfachen und unkomplizierten Algorithmus hielten.
// int mid =(low + high) / 2; // the bug
int mid = (low + high) >>> 1; // the fix
Die obige Zeile erscheint 11 mal im Quellcode !
Meine Fragen sind also:
- Wie wird mit solchen sich wiederholenden Java-Codes / Dokumentationen in der Praxis umgegangen? Wie werden sie entwickelt, gewartet und getestet?
- Beginnen Sie mit "dem Original" und machen Sie es so ausgereift wie möglich. Kopieren Sie es dann und fügen Sie es nach Bedarf ein und hoffen Sie, dass Sie keinen Fehler gemacht haben?
- Und wenn Sie im Original einen Fehler gemacht haben, beheben Sie ihn einfach überall, es sei denn, Sie möchten die Kopien löschen und den gesamten Replikationsprozess wiederholen?
- Und Sie wenden diesen Prozess auch für den Testcode an?
- Würde Java von einer Art Quellcode-Vorverarbeitung mit eingeschränkter Verwendung für diese Art von Dingen profitieren?
- Vielleicht hat Sun einen eigenen Präprozessor, der beim Schreiben, Verwalten, Dokumentieren und Testen dieser Art von sich wiederholendem Bibliothekscode hilft?
In einem Kommentar wurde ein anderes Beispiel angefordert, daher habe ich dieses aus 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
vsOrPredicate
(jeder erscheint 5 mal in seiner Klasse)"And("
vsOr("
(in den jeweiligentoString()
Methoden)#and
vs#or
(in den@see
Javadoc-Kommentaren)true
vsfalse
(inapply
;!
kann aus dem Ausdruck heraus umgeschrieben werden)-1 /* all bits on */
vs0 /* all bits off */
inhashCode()
&=
vs|=
inhashCode()
quelle
Antworten:
Für Leute, die unbedingt Leistung brauchen, sind Boxen und Unboxen und generierte Sammlungen und so weiter große No-No's.
Das gleiche Problem tritt beim Performance-Computing auf, bei dem Sie denselben Komplex benötigen, um sowohl für Float als auch für Double zu arbeiten (sagen Sie einige der Methoden, die in Goldberds Artikel " Was jeder Informatiker über Gleitkommazahlen wissen sollte " gezeigt werden ).
Es gibt einen Grund , warum Trove ‚s
TIntIntHashMap
läuft Kreise um Java ist ,HashMap<Integer,Integer>
wenn sie mit einer ähnlichen Menge an Daten arbeiten.Wie wird nun der Quellcode der Trove-Sammlung geschrieben?
Natürlich mit Quellcode-Instrumentierung :)
Es gibt mehrere Java-Bibliotheken für eine höhere Leistung (viel höher als die Standard-Java-Bibliotheken), die Codegeneratoren verwenden, um den wiederholten Quellcode zu erstellen.
Wir alle wissen, dass "Quellcode-Instrumentierung" böse ist und dass Codegenerierung Mist ist, aber so machen es immer noch Leute, die wirklich wissen, was sie tun (dh die Art von Leuten, die Sachen wie Trove schreiben) :)
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 * */
quelle
Wenn Sie unbedingt Code duplizieren müssen, folgen Sie den Beispielen, die Sie gegeben haben, und gruppieren Sie den gesamten Code an einem Ort, an dem Sie ihn leicht finden und beheben können, wenn Sie Änderungen vornehmen müssen. Dokumentieren Sie die Vervielfältigung und vor allem den Grund für die Vervielfältigung, damit jeder, der nach Ihnen kommt, beides kennt.
quelle
Aus Wikipedia Wiederholen Sie sich nicht (DRY) oder Vervielfältigung ist böse (DIE)
In einigen Kontexten kann der Aufwand zur Durchsetzung der DRY-Philosophie größer sein als der Aufwand zur Aufbewahrung separater Kopien der Daten. In einigen anderen Kontexten sind doppelte Informationen unveränderlich oder werden so streng kontrolliert, dass DRY nicht erforderlich ist.
Es gibt wahrscheinlich keine Antwort oder Technik, um solche Probleme zu verhindern.
quelle
Sogar ausgefallene Hosen-Sprachen wie Haskell haben sich wiederholenden Code ( siehe meinen Beitrag über Haskell und Serialisierung ).
Es scheint drei Möglichkeiten für dieses Problem zu geben:
Ich betrachte die Makros als anders als die Vorverarbeitung, da die Makros normalerweise in derselben Sprache sind, in der sich das Ziel befindet, da die Vorverarbeitung eine andere Sprache ist.
Ich denke, Lisp / Scheme-Makros würden viele dieser Probleme lösen.
quelle
Ich verstehe, dass Sun für den Java SE-Bibliothekscode so dokumentieren muss, und vielleicht tun es auch andere Bibliotheksautoren von Drittanbietern.
Ich halte es jedoch für eine völlige Verschwendung, Dokumentation in eine Datei wie diese in Code zu kopieren und einzufügen, der nur im eigenen Haus verwendet wird. Ich weiß, dass viele Leute anderer Meinung sind, weil dadurch ihre internen JavaDocs weniger sauber aussehen. Der Nachteil ist jedoch, dass der Code sauberer wird, was meiner Meinung nach wichtiger ist.
quelle
Java-Primitivtypen schrauben Sie, besonders wenn es um Arrays geht. Wenn Sie speziell nach Code mit primitiven Typen fragen, würde ich sagen, versuchen Sie einfach, diese zu vermeiden. Die Object [] -Methode ist ausreichend, wenn Sie die Box-Typen verwenden.
Im Allgemeinen benötigen Sie viele Komponententests, und es gibt wirklich nichts anderes zu tun, als auf Reflexion zurückzugreifen. Wie Sie sagten, es ist ein ganz anderes Thema, aber haben Sie keine Angst vor Reflexion. Schreiben Sie zuerst den DRYest-Code, den Sie können, und profilieren Sie ihn dann. Stellen Sie fest, ob der Treffer bei der Reflexionsleistung wirklich schlecht genug ist, um das Ausschreiben und Verwalten des zusätzlichen Codes zu rechtfertigen.
quelle
Sie können einen Codegenerator verwenden, um Variationen des Codes mithilfe einer Vorlage zu erstellen. In diesem Fall ist die Java-Quelle ein Produkt des Generators und der eigentliche Code ist die Vorlage.
quelle
Angesichts von zwei Codefragmenten, von denen behauptet wird, dass sie ähnlich sind, verfügen die meisten Sprachen nur über begrenzte Möglichkeiten, Abstraktionen zu erstellen, die die Codefragmente zu einem Monolithen vereinen. Um zu abstrahieren, wenn Ihre Sprache dies nicht kann, müssen Sie die Sprache verlassen: - {
Der allgemeinste "Abstraktions" -Mechanismus ist ein vollständiger Makroprozessor, der beim Instanziieren beliebige Berechnungen auf den "Makrokörper" anwenden kann (denken Sie an ein Post- oder String-Rewriting- System, das Turing-fähig ist). M4 und GPM sind typische Beispiele. Der C-Präprozessor gehört nicht dazu.
Wenn Sie einen solchen Makroprozessor haben, können Sie eine "Abstraktion" als Makro erstellen und den Makroprozessor auf Ihrem "abstrahierten" Quelltext ausführen, um den tatsächlichen Quellcode zu erzeugen, den Sie kompilieren und ausführen.
Sie können auch eingeschränktere Versionen der Ideen verwenden, die häufig als "Codegeneratoren" bezeichnet werden. Diese sind normalerweise nicht Turing-fähig, aber in vielen Fällen funktionieren sie gut genug. Dies hängt davon ab, wie komplex Ihre "Makro-Instanziierung" sein muss. (Der Grund , warum Menschen mit dem Template - Mechanismus ++ C verliebten sind , ist ths trotz seiner Hässlichkeit, es ist fähig Turing und so können die Menschen tun wirklich hässlich , aber erstaunlich Code - Generierung Aufgaben mit ihm). Eine andere Antwort hier erwähnt Trove, der anscheinend in der begrenzten, aber immer noch sehr nützlichen Kategorie liegt.
Wirklich allgemeine Makroprozessoren (wie M4) bearbeiten nur Text. Das macht sie leistungsfähig, aber sie beherrschen die Struktur der Programmiersprache nicht gut, und es ist wirklich umständlich, einen Generator in einen solchen Mcaro-Prozessor zu schreiben, der nicht nur Code erzeugen, sondern auch das generierte Ergebnis optimieren kann. Die meisten Codegeneratoren, auf die ich stoße, sind "diese Zeichenfolge in diese Zeichenfolgenvorlage einfügen" und können daher kein generiertes Ergebnis optimieren. Wenn Sie willkürlichen Code generieren und eine hohe Leistung erzielen möchten, benötigen Sie etwas, das Turing-fähig ist, aber die Struktur des generierten Codes versteht, damit er leicht manipuliert (z. B. optimiert) werden kann.
Ein solches Tool wird als Programmtransformationssystem bezeichnet . Ein solches Tool analysiert den Quelltext genau wie ein Compiler und führt dann Analysen / Transformationen durch, um einen gewünschten Effekt zu erzielen. Wenn Sie Markierungen in den Quelltext Ihres Programms einfügen können (z. B. strukturierte Kommentare oder Anmerkungen in Sprachen, in denen sie enthalten sind), die das Programmtransformationstool anweisen, was zu tun ist, können Sie es verwenden, um eine solche Abstraktionsinstanziierung, Codegenerierung und / oder Codeoptimierung. (Der Vorschlag eines Posters, sich in den Java-Compiler einzubinden, ist eine Variation dieser Idee.) Wenn Sie ein allgemeines Puprose-Transformationssystem verwenden (z. B. DMS Software Reengineering Tookit) , können Sie dies für praktisch jede Sprache tun.
quelle
Viele dieser Wiederholungen können jetzt dank Generika vermieden werden. Sie sind ein Glücksfall, wenn Sie denselben Code schreiben, bei 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 Werkzeug, um diese Art der Codeduplizierung zu reduzieren.
Um Ihre Frage zum Umgang mit Code zu beantworten, der unbedingt dupliziert werden muss ... Kennzeichnen 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 eine hatten.
quelle