Illusorische Code-Vervielfältigung

56

Der übliche Instinkt besteht darin, alle Code-Duplikationen zu entfernen, die Sie im Code sehen. Ich befand mich jedoch in einer Situation, in der die Vervielfältigung illusorisch ist .

Um die Situation genauer zu beschreiben: Ich entwickle eine Webanwendung und die meisten Ansichten sind im Grunde genommen gleich - sie zeigen eine Liste von Elementen an, aus denen der Benutzer scrollen und auswählen kann, eine zweite Liste, die ausgewählte Elemente enthält, und ein "Speichern" ", um die neue Liste zu speichern.

Es schien mir, dass das Problem einfach ist. Allerdings hat jede Ansicht ihre eigenen Macken - manchmal müssen Sie etwas neu berechnen, manchmal müssen Sie einige zusätzliche Daten speichern usw. Diese habe ich durch Einfügen von Callback-Hooks in den Hauptlogikcode gelöst.

Es gibt so viele winzige Unterschiede zwischen den Ansichten, dass sie immer weniger gewartet werden können, da ich Rückrufe für praktisch alle Funktionen bereitstellen muss und die Hauptlogik wie eine riesige Folge von Rückrufaufrufen aussieht. Am Ende spare ich keine Zeit oder Code, weil jede Ansicht einen eigenen Code hat, der ausgeführt wird - alles in Rückrufen.

Die Probleme sind:

  • Die Unterschiede sind so gering, dass der Code in allen Ansichten fast genau gleich aussieht.
  • Es gibt so viele Unterschiede, dass der Code beim Betrachten der Details kein bisschen gleich ist

Wie soll ich mit dieser Situation umgehen?
Ist es eine gute Lösung, wenn die Kernlogik ausschließlich aus Rückrufaufrufen besteht?
Oder sollte ich lieber den Code duplizieren und die Komplexität von Callback-basiertem Code aufheben?

Mael
quelle
38
Normalerweise finde ich es hilfreich, die Vervielfältigung zunächst loszulassen. Wenn ich ein paar Beispiele habe, ist es viel einfacher zu erkennen, was gemeinsam ist und was nicht, und eine Möglichkeit zu finden, die gemeinsamen Teile zu teilen.
Winston Ewert
7
Sehr gute Frage - eine Sache, die zu berücksichtigen ist, ist nicht nur die physische Vervielfältigung des Codes, sondern die semantische Vervielfältigung. Wenn eine Änderung an einem Teil des Codes zwangsläufig bedeutet, dass dieselbe Änderung auf die anderen Teile übertragen wird, ist dieser Teil wahrscheinlich ein Kandidat für eine Umgestaltung oder Abstraktion. Manchmal können Sie sich so weit normalisieren, dass Sie sich selbst in eine Falle locken. Daher würde ich auch die praktischen Auswirkungen der Behandlung der Duplizierung als semantisch unterschiedlich betrachten - sie können durch die Konsequenzen des Deduplizierungsversuchs aufgewogen werden.
Ant P
Denken Sie daran, dass Sie nur Code wiederverwenden können, der dasselbe bewirkt. Wenn Ihre App auf verschiedenen Bildschirmen unterschiedliche Aktionen ausführt, sind unterschiedliche Rückrufe erforderlich. Kein Wenn und Aber darüber.
Corsika
13
Meine persönliche Faustregel lautet: Wenn ich den Code an einer Stelle ändere, ist es ein Fehler, wenn ich nicht überall dieselbe Änderung vornehme? Wenn ja, ist es die schlechte Art der Vervielfältigung. Wenn ich mir nicht sicher bin, gehen Sie mit dem, was für den Moment besser lesbar ist. In Ihrem Beispiel sind die Verhaltensunterschiede beabsichtigt und werden nicht als Fehler angesehen. Daher ist eine gewisse Duplizierung in Ordnung.
Ixrec
Vielleicht möchten Sie etwas über aspektorientierte Programmierung lesen.
Ben Jackson

Antworten:

53

Letztendlich müssen Sie ein Urteil darüber fällen, ob Sie ähnlichen Code kombinieren sollen, um Doppelungen zu vermeiden.

Es scheint eine unglückliche Tendenz zu geben, Prinzipien wie "Wiederhole dich nicht" als Regeln zu betrachten, die jederzeit von Hand befolgt werden müssen. Tatsächlich handelt es sich hierbei nicht um universelle Regeln, sondern um Richtlinien, die Ihnen helfen sollen, über gutes Design nachzudenken und es zu entwickeln.

Wie alles im Leben müssen Sie die Vorteile im Vergleich zu den Kosten berücksichtigen. Wie viel duplizierter Code wird entfernt? Wie oft wird der Code wiederholt? Wie viel Aufwand wird es sein, ein allgemeineres Design zu schreiben? Inwieweit werden Sie den Code in Zukunft wahrscheinlich entwickeln? Und so weiter.

Ohne Ihren spezifischen Code zu kennen, ist dies unklar. Vielleicht gibt es eine elegantere Methode, um Duplikate zu entfernen (wie von LindaJeanne vorgeschlagen). Oder vielleicht gibt es einfach nicht genug wahre Wiederholung, um eine Abstraktion zu rechtfertigen.

Unzureichende Aufmerksamkeit für das Design ist eine Falle, aber auch Vorsicht vor Überdesign.


quelle
Ich denke, Ihr Kommentar zu "unglücklichen Tendenzen" und dem blinden Befolgen von Richtlinien ist vor Ort.
Mael
1
@Mael Wollen Sie damit sagen, dass Sie keine guten Gründe hätten, um das richtige Design zu erhalten , wenn Sie diesen Code in Zukunft nicht beibehalten würden? (keine Beleidigung, ich möchte nur wissen, was Sie darüber denken)
Spotted
2
@Mael Natürlich könnten wir es nur als eine unglückliche Wendung ansehen! : D Ich denke jedoch, wir sollten beim Schreiben von Code genauso streng mit uns selbst sein wie mit den anderen (ich betrachte mich als einen anderen, wenn ich meinen eigenen Code 2 Wochen nach dem Schreiben lese).
Spotted
2
@ user61852 dann würde dir The Codeless Code sehr missfallen .
RubberDuck
1
@ user61852, haha - aber was , wenn es nicht alle abhängen (auf Informationen , die nicht in der Frage gegeben)? Wenige Dinge sind weniger hilfreich als übermäßige Gewissheit.
43

Denken Sie daran, dass es bei DRY um Wissen geht . Es spielt keine Rolle, ob zwei Codeteile ähnlich, identisch oder völlig unterschiedlich aussehen. Entscheidend ist, ob in beiden das gleiche Wissen über Ihr System vorhanden ist.

Ein Teil des Wissens kann eine Tatsache sein ("die maximal zulässige Abweichung vom beabsichtigten Wert beträgt 0,1%") oder ein Aspekt Ihres Prozesses ("diese Warteschlange enthält niemals mehr als drei Elemente"). Es ist im Wesentlichen jede einzelne Information, die in Ihrem Quellcode kodiert ist.

Wenn Sie also entscheiden, ob es sich bei etwas um eine Vervielfältigung handelt, die entfernt werden sollte, fragen Sie, ob es sich um eine Vervielfältigung von Wissen handelt. Wenn nicht, ist es wahrscheinlich eine zufällige Vervielfältigung. Wenn Sie später eine ähnliche Komponente erstellen möchten, bei der sich das scheinbar vervielfältigte Teil unterscheidet, kann es zu Problemen kommen, wenn Sie es an eine übliche Stelle extrahieren.

Ben Aaronson
quelle
12
Diese! Der Fokus von DRY liegt auf der Vermeidung doppelter Änderungen .
Matthieu M.
Das ist sehr hilfreich.
1
Ich dachte, der Fokus von DRY war es, sicherzustellen, dass es keine zwei Codebits gibt, die sich identisch verhalten sollten, aber nicht. Das Problem ist nicht die doppelte Arbeit, da Codeänderungen zweimal angewendet werden müssen. Das eigentliche Problem ist, wenn eine Codeänderung zweimal angewendet werden muss, dies jedoch nicht der Fall ist.
gnasher729
3
@ gnasher729 Ja, das ist der Punkt. Wenn zwei Codeteile doppeltes Wissen aufweisen , ist zu erwarten, dass sich das eine und das andere ändern müssen, was zu dem von Ihnen beschriebenen Problem führt. Wenn sie gelegentlich dupliziert werden , muss der eine möglicherweise gleich bleiben, wenn der andere geändert werden muss. In diesem Fall, wenn Sie eine gemeinsame Methode (oder was auch immer) extrahiert haben, haben Sie jetzt ein anderes Problem
Ben Aaronson
1
Weitere wichtige Vervielfältigungen und versehentliche Vervielfältigungen finden Sie unter Ein versehentlicher Doppelgänger in Ruby und Ich habe meinen Code getrocknet und jetzt ist es schwierig, mit ihm zu arbeiten. Was ist passiert? . Zufällige Duplikate treten auch auf beiden Seiten einer Kontextgrenze auf . Zusammenfassung: Führen Sie nur Duplikate zusammen, wenn es für ihre Clients sinnvoll ist, diese Abhängigkeiten gleichzeitig zu ändern .
Eric
27

Haben Sie darüber nachgedacht, ein Strategiemuster zu verwenden ? Sie hätten eine View-Klasse, die den gemeinsamen Code und die Routinen enthält, die von mehreren Views aufgerufen werden. Untergeordnete Elemente der View-Klasse enthalten den für diese Instanzen spezifischen Code. Sie alle würden die gemeinsame Schnittstelle verwenden, die Sie für die Ansicht erstellt haben, und daher wären die Unterschiede gekapselt und kohärent.

LindaJeanne
quelle
5
Nein, darüber habe ich nicht nachgedacht. Vielen Dank für den Vorschlag. Nach einer kurzen Lektüre über das Strategiemuster scheint es etwas zu sein, nach dem ich suche. Ich werde es auf jeden Fall weiter untersuchen.
Mael
3
Es gibt ein Muster für die Vorlagenmethode . Sie können auch berücksichtigen, dass
Shakil
5

Was ist das Potenzial für Veränderungen? Beispielsweise verfügt unsere Anwendung über 8 verschiedene Geschäftsbereiche mit einem Potenzial von 4 oder mehr Benutzertypen für jeden Bereich. Ansichten werden basierend auf dem Benutzertyp und dem Bereich angepasst.

Zu Beginn wurde dieselbe Ansicht verwendet, wobei hier und da einige Überprüfungen durchgeführt wurden, um festzustellen, ob verschiedene Dinge angezeigt werden sollten. Im Laufe der Zeit haben einige Geschäftsbereiche entschieden, drastisch andere Dinge zu tun. Am Ende haben wir im Wesentlichen auf eine Ansicht (Teilansichten im Fall von ASP.NET MVC) pro Funktionsbereich pro Geschäftsbereich migriert. Nicht alle Geschäftsbereiche haben die gleiche Funktionalität, aber wenn einer die Funktionalität eines anderen haben möchte, erhält dieser Bereich eine eigene Ansicht. Es ist viel weniger umständlich für das Verständnis des Codes sowie für die Testbarkeit. Wenn Sie beispielsweise eine Änderung für einen Bereich vornehmen, führt dies nicht zu einer unerwünschten Änderung für einen anderen Bereich.

Wie @ dan1111 bereits erwähnt, kann es zu einem Urteil kommen. Mit der Zeit können Sie feststellen, ob es funktioniert oder nicht.

ps2goat
quelle
2

Ein Problem könnte sein, dass Sie eine Schnittstelle (theoretische Schnittstelle, keine Sprachfunktion) für nur eine Ebene der Funktionalität bereitstellen:

A(a,b,c) //a,b,c are your callbacks or other dependencies

Anstelle mehrerer Ebenen, je nachdem, wie viel Kontrolle erforderlich ist:

//high level
A(a,b,c)
//lower
A myA(a,b)
B(myA,c)
//even lower
A myA(a)
B myB(myA,b)
C myC(myB,c)
//all the way down to you just having to write the code yourself

Soweit ich verstanden habe, legen Sie nur die High-Level-Schnittstelle (A) offen und verbergen die Implementierungsdetails (die anderen Dinge dort).

Das Verbergen von Implementierungsdetails hat Vorteile und Sie haben gerade einen Nachteil festgestellt - die Kontrolle ist begrenzt, es sei denn, Sie fügen explizit Funktionen für jede einzelne Sache hinzu, die bei direkter Verwendung der Benutzeroberfläche (n) auf niedriger Ebene möglich gewesen wäre.

Sie haben also zwei Möglichkeiten. Entweder verwenden Sie nur die Low-Level-Schnittstelle, verwenden die Low-Level-Schnittstelle, weil die Pflege der High-Level-Schnittstelle zu aufwendig war, oder Sie legen sowohl High- als auch Low-Level-Schnittstellen offen. Die einzig sinnvolle Option besteht darin, sowohl High- als auch Low-Level-Schnittstellen (und alles dazwischen) anzubieten, vorausgesetzt, Sie möchten redundanten Code vermeiden.

Wenn Sie dann eine andere Ihrer Sachen schreiben, sehen Sie sich alle verfügbaren Funktionen an, die Sie bisher geschrieben haben (unzählige Möglichkeiten, zu entscheiden, welche wiederverwendet werden können), und setzen Sie sie zusammen.

Verwenden Sie ein einzelnes Objekt, bei dem Sie nur wenig Kontrolle benötigen.

Verwenden Sie die Funktionalität der untersten Ebene, wenn eine gewisse Verrücktheit auftreten muss.

Es ist auch nicht sehr schwarz-weiß. Vielleicht kann Ihre große High-Level-Klasse vernünftigerweise alle möglichen Anwendungsfälle abdecken. Vielleicht sind die Anwendungsfälle so unterschiedlich, dass nur die primitive Funktionalität der untersten Ebene ausreicht. Es liegt an Ihnen, das Gleichgewicht zu finden.

Wasserlimon
quelle
1

Es gibt bereits andere nützliche Antworten. Ich werde meine hinzufügen.

Vervielfältigung ist schlecht, weil

  1. es wirft den Code durcheinander
  2. es überfrachtet unsere Zusammenstellung des Codes aber am wichtigsten
  3. denn wenn man etwas ändern hier etwas und Sie auch ändern müssen dort , könnten Sie vergessen / einführen Bugs / .... und es ist schwer zu nie vergessen werden .

Der Punkt ist also: Du eliminierst keine Duplizierung, oder weil jemand sagte, es sei wichtig. Sie tun es, weil Sie Fehler / Probleme reduzieren möchten. In Ihrem Fall scheint es, dass Sie, wenn Sie etwas in einer Ansicht ändern, wahrscheinlich nicht dieselbe Zeile in allen anderen Ansichten ändern müssen . Sie haben also scheinbare Vervielfältigung , keine tatsächliche Vervielfältigung.

Ein weiterer wichtiger Punkt ist, niemals etwas von Grund auf neu zu schreiben, was jetzt nur auf der Grundlage eines Prinzips funktioniert, wie Joel sagte (man hätte schon von ihm hören können ...). Wenn Ihre Ansichten also funktionieren, verbessern Sie sich Schritt für Schritt und geraten Sie nicht an den "schlimmsten strategischen Fehler, den ein Software-Unternehmen machen kann".

Francesco
quelle