2009-09-01 11 views
19

Ich überprüfe gerade ein sehr altes C++ Projekt und sehe dort viel Code Duplikation.Gibt es einen gültigen Grund für die Code-Duplizierung?

Zum Beispiel gibt es eine Klasse mit 5 MFC-Nachrichtenhandlern, die jeweils 10 identische Codezeilen enthalten. Oder es gibt ein 5-zeiliges Snippet für eine sehr spezifische String-Transformation hier und da. Die Reduzierung von Code-Duplikaten ist in diesen Fällen überhaupt kein Problem.

Aber ich habe das seltsame Gefühl, dass ich etwas missverstehen könnte und dass es ursprünglich einen Grund für diese Vervielfältigung gab.

Was könnte ein gültiger Grund sein, Code zu duplizieren?

Antwort

14

Als ich anfing zu programmieren, schrieb ich eine App, wo ich eine Reihe ähnlicher Funktionen hatte, die ich in eine nette kleine 20-30 Zeilenfunktion einwickelte ... Ich war sehr stolz auf mich selbst für das Schreiben solch eines eleganten Stückes von Code.

Kurz danach änderte der Client den Prozess in ganz bestimmten Fällen, dann wieder, dann wieder, dann wieder, und wieder und wieder .... (viele, viele weitere Male) Mein eleganter Code wurde zu einem sehr schwierigen, hackish, buggy, & wartungsarm.

Ein Jahr später, als ich aufgefordert wurde, etwas sehr ähnliches zu tun, entschied ich mich bewusst, DRY zu ignorieren.Ich stellte den grundlegenden Prozess zusammen und generierte alle doppelten Code. Der doppelte Code wurde dokumentiert und ich speicherte die Vorlage, die zum Generieren des Codes verwendet wurde. Wenn der Klient nach bestimmten bedingten Änderungen fragte (wie, wenn x == y^z + b dann 1 + 2 == 3.42), war es ein Stück Kuchen. Es war unglaublich einfach zu pflegen & Änderung.

Rückblickend hätte ich wahrscheinlich viele dieser Probleme mit Funktionszeigern und Prädikaten lösen können, aber mit dem Wissen, das ich damals hatte, glaube ich immer noch an diesen speziellen Fall, das war die beste Entscheidung.

+0

+1. Veränderte Anforderungen sind oft ein Mörder für DRY. –

+13

Veränderte Anforderungen sind ein Mörder. DRY ist nicht besonders. –

16

Faulheit, das ist der einzige Grund, den ich mir vorstellen kann.

Auf eine ernstere Notiz. Der einzige gültige Grund, an den ich denken kann, sind Änderungen am Ende des Produktzyklus. Diese neigen dazu, viel genauer untersucht zu werden, und die kleinste Veränderung hat tendenziell die höchste Erfolgsrate. In diesem eingeschränkten Fall ist es einfacher, eine Code-Duplizierungsänderung durchzusetzen, als eine kleinere Änderung umzuformen.

Noch hinterlässt einen schlechten Geschmack in meinem Mund.

+0

Die Art der umgekehrten Faulheit, nicht wahr? Ich meine, es wäre viel fauler, eine Funktion zu machen und es überall zu rufen ... – Justicle

+4

@Justicle Wahr, aber wenn Sie nur diese eine Funktion beenden und Ihren Code ausprobieren möchten, ist es einfacher, die 5 Zeilen Code dort hinzuzufügen, anstatt darüber nachzudenken, Parameter/Rückgabetypen und all die anderen Dinge, die mit einer Funktion geliefert werden, zu übergeben. – DeusAduro

+2

Die Copy & Paste-Methode bedeutet auch, dass Sie den Originalcode überhaupt nicht ändern müssen, was bedeutet, dass die Wahrscheinlichkeit, dass etwas kaputt geht, geringer ist - ein wichtiges Problem, wenn Sie sich in der Zeit unmittelbar vor einer Veröffentlichung befinden. –

3

Die einzige "gültige" Sache, die ich daraus sehen kann, ist, wenn diese Codezeilen unterschiedlich waren, konvergierte dann durch spätere Bearbeitungen zur selben Sache. Ich hatte das schon einmal mit mir, aber nicht allzu oft.

Dies ist natürlich, wenn es Zeit ist, dieses gemeinsame Code-Segment in neue Funktionalität zu zerlegen.

Das gesagt, ich kann mir keinen vernünftigen Weg vorstellen, um doppelten Code zu rechtfertigen. Schau, warum es schlecht ist.

Es ist schlecht, weil eine Änderung an einem Ort eine Änderung an mehreren Stellen erfordert. Dies ist eine erhöhte Zeit mit einer Chance auf Fehler. Wenn Sie dies ausschließen, pflegen Sie den Code an einem einzigen Arbeitsplatz. Wenn Sie ein Programm schreiben, schreiben Sie es nicht zweimal, warum sollte eine Funktion anders sein?

0

Es gibt keinen guten Grund für die Code-Duplizierung.

Siehe das Refactor Mercilessly Entwurfsmuster.

Der ursprüngliche Programmierer war entweder in Eile, einen Termin einzuhalten oder faul. Fühlen Sie sich frei, den Code zu überarbeiten und zu verbessern.

+1

-1, Während der Fall dieses Fragestellers ist kein guter Grund, und fast jeder andere Fall von Doppelarbeit ist auch nicht, natürlich gibt es gültige Gründe (siehe die Antworten). – orip

3

Für diese Art von Code-Duplizierung (viele Zeilen dupliziert viele Male), würde ich sagen:

  • entweder Faulheit (Sie nur einige Code hier und dort einfügen, ohne sich um irgendwelche Gedanken machen zu müssen Auswirkungen könnte es auf andere Teile der Anwendung haben - während des Schreibens einer neuen Funktion und die Verwendung an zwei Stellen könnte, nehme ich an, einige Auswirkungen haben)
  • oder keine gute Praxis kennen (Code wiederverwenden, trennen verschiedene Aufgaben in verschiedenen Funktionen/Methoden)

Wahrscheinlich ist die erste Lösung, aber von dem, was habe ich in der Regel :-(

gesehen

beste Lösung, die ich gegen das gesehen habe: haben Ihre developpers durch die Aufrechterhaltung einige alte Anwendung zu starten, wenn sie eingestellt werden - - das wird ihnen beibringen, dass diese Art von Sache nicht gut ist ... Und sie werden verstehen, warum, das ist der wichtigste Teil.

Splitting Code in mehrere Funktionen, die Wiederverwendung von Code der richtige Weg, und alles, was oft mit Erfahrung kommen - oder Sie haben nicht die richtigen Leute eingestellt ;-)

+0

Meiner Erfahrung nach haben jüngere Entwickler, die begonnen haben, hässlichen Code zu pflegen (und keinen guten Code zu sehen bekommen), nur eine "Patch-es-bis-es-funktioniert" -Mentalität erworben. –

+0

@wcoenen Klingt so, als müssten Sie bessere Junior-Entwickler rekrutieren. Ein kleiner Hauch von OCD kann eine gute Sache sein. –

2

vor langer Zeit, als ich früher Wenn Sie Grafiken programmieren, würden Sie in einigen speziellen Fällen doppelten Code verwenden, um JMP-Anweisungen auf niedriger Ebene zu vermeiden, die im Code generiert werden (dies würde die Leistung verbessern, indem der Sprung zur Beschriftung/Funktion vermieden wird). Es war eine Möglichkeit zu optimieren und ein Pseudo "Inlining" zu machen.

In diesem Fall glaube ich nicht, dass sie es deshalb taten, heh.

1

Klingt wie der ursprüngliche Autor war entweder unerfahren und/oder wurde hart auf Zeit gedrückt. Die meisten erfahrenen Programmierer bündeln Dinge, die wiederverwendet werden, weil später weniger Wartung benötigt wird - eine Form der Faulheit.

Die einzige Sache, die Sie überprüfen sollten, ist, wenn es irgendwelche Nebenwirkungen gibt, wenn der kopierte Code auf einige globale Daten zugreift, ein bisschen Refactoring möglicherweise benötigt wird.

edit: Damals, als Compiler noch beschissen und Optimierer waren, konnte es passieren, dass man aufgrund eines Fehlers im Compiler einen solchen Trick machen musste, um einen Fehler zu umgehen. Vielleicht ist es so etwas? Wie alt ist alt?

12

Sie können dies tun, um sicherzustellen, dass zukünftige Änderungen in einem Teil nicht unbeabsichtigt den anderen Teil ändern. zum Beispiel

Do_A_Policy() 
{ 
    printf("%d",1); 
    printf("%d",2); 
} 

Do_B_Policy() 
{ 
    printf("%d",1); 
    printf("%d",2); 
} 

Sie können jetzt „Code-Duplizierung“ mit Funktion wie diese betrachten verhindern:

first_policy() 
{ 
printf("%d",1); 
printf("%d",2); 
} 

Do_A_Policy() 
{ 
first_policy() 
} 

Do_B_Policy() 
{ 
first_policy() 
} 

Es besteht jedoch die Gefahr, dass einige andere Programmierer ändern möchten Do_A_Policy() und tun Wenn Sie also first_policy() ändern, wird der Nebeneffekt der Änderung von Do_B_Policy() auftreten, ein Nebeneffekt, den der Programmierer möglicherweise nicht bemerkt. so kann diese Art von "Code-Duplikation" als ein Sicherheitsmechanismus gegen diese Art von zukünftigen Änderungen im Programm dienen.

+1

Nun, klingt für mich wie 'first_policy' müsste einen Parameter der Art nehmen. – GManNickG

+2

Dieses Beispiel schreit nach einem Komponententest. –

+1

Ich sehe, wohin du gehst, aber ich denke, es ist viel aufrechterhaltbar, die Logik in 'first_policy()' zu faktorisieren, wie du es getan hast. Wenn Sie später alle Verwendungen dieser Logik finden müssen, ist es viel einfacher, alle Aufrufe von 'first_policy()' zu finden als "alle Paare von' printf() 'Anweisungen, die wie folgt aussehen". Ein Coder, der die Semantik einer Funktion ändert, ohne alle Call-Sites zu überprüfen, muss ...* Überredet * dies nicht zu tun. :) –

6

Manchmal haben Methoden und Klassen, die domänenweise nichts gemeinsam haben, sich aber in der Implementierung sehr ähnlich.In diesen Fällen ist es oft besser, Code-Duplikation als zukünftige Änderungen durchzuführen, die diese Implementierungen nicht in etwas verzweigen, das nicht das Gleiche ist.

+1

können Sie ein echtes Beispiel für eine solche Situation geben? – flybywire

+0

@flybywire das sind viele und nicht tun, wie Cwap vorschlagen, ist ein ziemlich häufig (und oft ovrlooked) Designfehler. Es führt oft zu vielen Verzweigungen/Einschaltzuständen, wann immer Sie Logik ausführen müssen, und weil das Refactoring normalerweise lokal und nicht über die Länder –

2

Wenn verschiedene Aufgaben aus Versehen ähnlich sind, ist das Wiederholen derselben Aktionen an zwei Stellen nicht unbedingt eine Duplizierung. Wenn sich die Handlungen an einem Ort ändern, ist es wahrscheinlich, dass sie sich auch an anderen Orten ändern? Dann ist dies eine Duplizierung, die Sie vermeiden oder umstrukturieren sollten.

Auch manchmal - auch wenn Logik doppelt vorhanden ist - sind die Kosten für die Reduzierung von Duplikaten zu hoch. Dies kann besonders dann passieren, wenn es nicht nur um die Duplizierung von Code geht: Wenn Sie beispielsweise Datensätze mit bestimmten Feldern haben, die sich an verschiedenen Stellen wiederholen (DB-Tabellendefinition, C++ - Klasse, textbasierte Eingabe), können Sie dies normalerweise reduzieren Duplizierung erfolgt mit Code-Generierung. Dies erhöht die Komplexität Ihrer Lösung. Fast immer zahlt sich diese Komplexität aus, manchmal aber auch nicht - es ist Ihr Kompromiss.

1

Bei großen Projekten (die mit einer Code-Base so groß wie ein GB) ist es möglich bestehende API zu verlieren. Dies liegt in der Regel an unzureichender Dokumentation oder daran, dass der Programmierer den ursprünglichen Code nicht finden kann. daher doppelter Code.

Abkochung zu Faulheit oder schlechte Review-Praxis.

EDIT:

Eine weitere Möglichkeit besteht darin, dass es in diesen Verfahren zusätzlicher Code haben kann, die auf dem Weg entfernt wurden.

Haben Sie sich den Überarbeitungsverlauf in der Datei angesehen?

14

Neben unerfahren zu sein, ist es, warum duplizierten Code Ereignisse auftauchen könnte:

Keine Zeit richtig

Refactoring Die meisten von uns sind in einer realen Welt zu arbeiten, wo reale Zwänge uns zwingen, sich schnell zu bewegen zu echten Problemen, anstatt über die Nettigkeit des Codes nachzudenken. Also kopieren wir & einfügen und weitermachen. Wenn ich später sehe, dass der Code mehrere Male dupliziert wird, ist es das Zeichen, dass ich etwas mehr Zeit damit verbringen muss und alle Instanzen zu einem konvergieren muss.

Generalisierung des Codes nicht möglich/nicht ‚ziemlich‘ aufgrund von Sprach Zwänge

Lassen Sie uns sagen, dass tief im Inneren einer Funktion, die Sie mehrere Aussagen haben, die sich stark von Instanz zu Instanz gleichen duplizierten Code unterscheiden. Zum Beispiel: Ich habe eine Funktion, die 2d Miniaturbilder für das Video zeichnet, und es ist mit der Berechnung jeder Miniaturbildposition eingebettet. Um den Treffer-Test zu berechnen (Index-Index aus Klick-Position berechnen) benutze ich denselben Code, aber ohne zu malen.

Sie sind nicht sicher, dass es Generalisierung bei allen auf den ersten

doppelten Code sein, und später beobachten, wie es sich entwickeln wird. Da wir Software schreiben, können wir "so spät wie möglich" Änderungen an der Software vornehmen, da alles "weich" und veränderbar ist.

Ich füge mehr hinzu, wenn ich mich an etwas anderes erinnere.


Später hinzugefügt ...

Schleife

In der Zeit Abrollen vor Compiler intelligent waren wie Einstein und Hawking kombiniert, hatten Sie die Schlaufen oder Inline-Code entrollen, schneller zu sein. Schleifen-Abrollung wird dazu führen, dass Ihr Code dupliziert wird, und wahrscheinlich um ein paar Prozent schneller, hat der Compiler das sowieso nicht gemacht.

+3

+1 für Ihren letzten Punkt ausgeführt wird - es ist das gleiche Prinzip wie die vorzeitige Optimierung. –

4

Der gültige Grund, den ich mir vorstellen kann: Wenn der Code viel komplexer wird, um die Duplizierung zu vermeiden. Im Grunde ist das der Ort, an dem man in mehreren Methoden fast das Gleiche macht - aber eben nicht ganz dasselbe. Natürlich können Sie spezielle Parameter wie Zeiger auf verschiedene Elemente, die geändert werden müssen, refaktorieren und hinzufügen. Aber die neue, refactored Methode kann zu kompliziert werden.

Beispiel (Pseudo-Code):

procedure setPropertyStart(adress, mode, value) 
begin 
    d:=getObject(adress) 
    case mode do 
    begin 
    single: 
     d.setStart(SingleMode, value); 
    delta: 
     //do some calculations 
     d.setStart(DeltaSingle, calculatedValue); 
    ... 
end; 

procedure setPropertyStop(adress, mode, value) 
begin 
    d:=getObject(adress) 
    case mode do 
    begin 
    single: 
     d.setStop(SingleMode, value); 
    delta: 
     //do some calculations 
     d.setStop(DeltaSingle, calculatedValue); 
    ... 
end; 

Sie könnten den Methodenaufruf Refactoring out (setXXX) irgendwie - aber abhängig von der Sprache könnte es schwierig sein (vor allem mit Vererbung). Es ist Code-Duplizierung, da der größte Teil des Körpers für jede Eigenschaft gleich ist, aber es kann schwierig sein, die gemeinsamen Teile umzuformen.

Kurz gesagt - wenn die Refaktor-Methode Faktoren komplizierter ist, würde ich mit Code-Duplizierung gehen, obwohl es "böse" ist (und böse bleiben wird).

+0

+1 - der wichtige Teil hier ist "abhängig von der Sprache", aber ich stimme zu, dass es auch in einfachen Fällen wie diesem passieren kann. – orip

1

Alle Antworten sieht richtig aus, aber ich denke, es gibt eine andere Möglichkeit. Vielleicht gibt es Überlegungen zur Leistung, da die Dinge, die Sie sagen, erinnert mich "Inline-Code". Inline-Funktionen, die sie aufrufen, sind immer schneller. Vielleicht wurde der Code, den Sie betrachten, zuerst vorverarbeitet?

+0

Moderne Sprachen lassen Sie Funktionen schreiben, und dann einen Hinweis darauf, dass sie inline sein sollten. Dadurch erhalten Sie das Beste aus beiden Welten: Inlining und Vermeidung von redundantem Code. –

26

Eine gute Lektüre darüber ist large scale c++ software design von John Lakos.

Er hat viele gute Punkte über Code-Duplikation, wo es ein Projekt helfen oder behindern könnte.

Der wichtigste Punkt zu fragen, wenn Doppelarbeit zu entfernen entscheiden oder Code kopieren:

Wenn diese Methode in Zukunft ändert, will ich das Verhalten in der duplizierten Methode ändern, oder es muss die bleiben wie es ist?

Schließlich enthalten Methoden (Geschäfts) Logik, und manchmal wollen Sie die Logik für jeden Anrufer ändern, manchmal nicht. Hängt von den Umständen ab.

Am Ende dreht sich alles um Wartung, nicht um hübsche Quelle.

+0

Einverstanden. Die Leute werden über strukturelle Veränderungen paranoid, besonders wenn es bereits funktioniert und Releases drohen. –

+7

"Am Ende dreht sich alles um Wartung, nicht um schöne Quellen." <- Mucho wichtig !! :) – cwap

+0

Die richtige Antwort ist, die allgemeine Logik in eine separate Funktion zu setzen, 'common()', und dann haben 'businesslogic1()' und 'businessphlogic2()' beide das nennen. Wenn (nur) 'busineslogic1()' in Zukunft geändert werden muss, * dann * kopiere & füge von 'common()' hinein und führe Änderungen durch. (Aber nur, wenn Sie 'common()' nicht einfach so parametrieren können, dass beide Fälle behandelt werden.) –

1

Ich habe keine Probleme mit dupliziertem Code, wenn es von einem Quellcode-Generator erzeugt wird.

2

Ich kenne nicht viele gute Gründe für Code-Duplikation, aber anstatt in Fuß zuerst zu refactoring springen, ist es wahrscheinlich besser, nur die Teile des Codes, die Sie tatsächlich ändern, Refactoring, anstatt eine große Codebase zu ändern Du verstehst das noch nicht ganz.

1

Etwas, das uns dazu brachte, Code zu duplizieren, war unser Pixelmanipulationscode. Wir arbeiten mit sehr großen Bildern und der Overhead des Funktionsaufrufs verzehrte in der Größenordnung von 30% unserer pro-Pixel-Zeit.

Die Duplizierung des Pixelmanipulationscodes ermöglichte uns eine um 20% schnellere Bilddurchquerung auf Kosten der Komplexität des Codes.

Dies ist offensichtlich ein sehr seltener Fall, und am Ende hat es unsere Quelle erheblich aufgebläht (eine 300 Zeilen Funktion ist jetzt 1200 Zeilen).

+0

Haben Sie versucht, eine Compiler-Anweisung zu verwenden, um die Funktionsaufrufe inline zu erzwingen? – sharptooth

+0

Wir haben das Inline-Schlüsselwort ausprobiert, aber es hat die Funktion nicht in allen Fällen, die wir brauchten, zuverlässig dargestellt. –

0

meiner bescheidenen Meinung nach gibt es keinen Platz für Code-Duplizierung. haben Sie einen Blick, zum Beispiel bei this wikipedia article

oder lassen Sie uns zu Larry Wall Zitat beziehen:

„Wir ermutigen Sie die drei großen Tugenden eines Programmierers zu entwickeln: Faulheit, Ungeduld und Hybris. "

Es ist ziemlich klar, dass Code-Duplizierung hat nichts mit "Faulheit" zu tun. haha;)

+0

Wirklich? Aufgrund der Magie von Copy/Paste ist das Versenden von Spam "HAHAHAHAHAHAHAHAHAHAHAHAHAHAHA" im Kommentarfeld viel einfacher als das Schreiben von etwas Nachdenklichem. Doppelter Code ist einfach zu schreiben. Es ist oft die faule Lösung. – jalf

+1

Die faule Lösung wäre, einen Kaffee zu holen und darüber nachdenken, wie man anstelle von c & p DRY (was dumm wäre). Es gibt einen Unterschied zwischen intelligenten Leuten, die faul sind und Leuten, die faul sind. Programmierer (die schlau sein sollen) reduzieren die Arbeit, während andere auf der Couch sitzen und fett werden. – atamanroman

0

Da es das "Strategie-Muster" gibt, gibt es keinen gültigen Grund für doppelten Code. Nicht eine einzige Codezeile muss dupliziert werden, alles andere ist episch fehlgeschlagen.