it-swarm.com.de

Was muss ich beachten, wenn die Prinzipien DRY und KISS) nicht kompatibel sind?

Das DRY-Prinzip zwingt die Programmierer manchmal dazu, komplexe, schwer zu wartende Funktionen/Klassen zu schreiben. Code wie dieser neigt dazu, komplexer zu werden und im Laufe der Zeit schwieriger zu warten. Verstoß gegen das KISS-Prinzip .

Zum Beispiel, wenn mehrere Funktionen etwas Ähnliches tun müssen. Die übliche Lösung DRY) besteht darin, eine Funktion zu schreiben, die verschiedene Parameter verwendet, um geringfügige Abweichungen in der Verwendung zu berücksichtigen.

Der Vorteil liegt auf der Hand: DRY = ein Ort, an dem Änderungen vorgenommen werden können usw.).

Der Nachteil und der Grund, warum es verletzt KISS) ist, dass Funktionen wie diese dazu neigen, mit der Zeit mit immer mehr Parametern immer komplexer zu werden. Am Ende werden die Programmierer große Angst davor haben Nehmen Sie Änderungen an solchen Funktionen vor, oder sie verursachen Fehler in anderen Anwendungsfällen der Funktion.

Persönlich halte ich es für sinnvoll, gegen das Prinzip von DRY] zu verstoßen, damit es folgt KISS Prinzip).

Ich hätte lieber 10 supereinfache Funktionen, die ähnlich sind, als eine superkomplexe Funktion.

Ich würde lieber etwas mühsames, aber einfaches tun (die gleiche Änderung oder ähnliche Änderung an 10 Stellen vornehmen), als eine sehr beängstigende/schwierige Änderung an einer Stelle vorzunehmen.

Offensichtlich ist der ideale Weg, es als KISS wie möglich) zu machen, ohne DRY zu verletzen. Aber manchmal scheint es unmöglich.

Eine Frage, die sich stellt, lautet: "Wie oft ändert sich dieser Code?" Dies bedeutet, dass es relevanter ist, es trocken zu machen, wenn es sich häufig ändert. Ich bin anderer Meinung, weil das Ändern dieser einen komplexen DRY - Funktion oft dazu führt, dass sie komplexer wird und mit der Zeit noch schlimmer wird.

Im Grunde genommen denke ich im Allgemeinen KISS> DRY.

Was denken Sie? In welchen Fällen sollte DRY immer KISS gewinnen und umgekehrt? Welche Dinge berücksichtigen Sie bei der Entscheidung? Wie vermeiden Sie die Situation?

71
user158443

KISS ist subjektiv. DRY ist leicht zu übertreiben. Beide haben gute Ideen, aber beide sind leicht zu missbrauchen. Der Schlüssel ist das Gleichgewicht.

KISS ist wirklich im Auge Ihres Teams. Sie wissen nicht, was KISS ist. Ihr Team tut es. Zeigen Sie ihnen Ihre Arbeit und sehen Sie, ob sie es für einfach halten. Sie beurteilen dies schlecht, weil Sie bereits wissen, wie es funktioniert. Finden Sie heraus, wie schwer Ihr Code für andere zu lesen ist.

Bei DRY geht es nicht darum, wie Ihr Code aussieht. Sie können echte DRY -Probleme nicht erkennen, indem Sie nach identischem Code suchen. Ein echtes DRY -Problem könnte sein, dass Sie dasselbe Problem mit völlig unterschiedlichem Aussehen lösen Code an einem anderen Ort. Sie verletzen nicht DRY, wenn Sie identischen Code verwenden, um ein anderes Problem an einem anderen Ort zu lösen. Warum? Weil sich verschiedene Probleme unabhängig voneinander ändern können. Jetzt muss man sich ändern und der andere nicht.

Treffen Sie Designentscheidungen an einem Ort. Verbreite keine Entscheidung. Aber falten Sie nicht jede Entscheidung, die gerade gleich aussieht, an derselben Stelle. Es ist in Ordnung, sowohl x als auch y zu haben, auch wenn beide auf 1 gesetzt sind.

Mit dieser Perspektive setze ich niemals KISS oder DRY) über die andere. Ich sehe nicht annähernd die Spannung zwischen ihnen. Ich schütze mich vor Missbrauch von Dies sind beide wichtige Prinzipien, aber auch keine Silberkugel.

144
candied_orange

Ich schrieb darüber bereits in ein Kommentar bis eine andere Antwort von candied_orange zu einem ähnliche Frage und berührte es auch etwas in einem andere Antwort , aber es lohnt sich zu wiederholen:

DRY ist ein süßes Akronym aus drei Buchstaben für eine Mnemonik "Don't Repeat Yourself", die in dem Buch The Pragmatic Programmer geprägt wurde, in dem es sich um ein - gesamter Abschnitt mit 8,5 Seiten . Es hat auch eine mehrseitige Erklärung und Diskussion im Wiki .

Die Definition im Buch lautet wie folgt:

Jedes Wissen muss eine einzige, eindeutige und maßgebliche Darstellung innerhalb eines Systems haben.

Beachten Sie, dass es nachdrücklich um nicht geht, Duplikate zu entfernen. Es geht um identifizierend welches der Duplikate das kanonische ist. Wenn Sie beispielsweise einen Cache haben, enthält der Cache Werte, die Duplikate von etwas anderem sind. Allerdings muss sehr deutlich gemacht werden, dass der Cache nicht die kanonische Quelle ist.

Das Prinzip ist nicht die drei Buchstaben DRY. Es sind diese ungefähr 20 Seiten im Buch und im Wiki.

Das Prinzip ist auch eng mit OAOO verwandt, einem nicht so niedlichen Akronym aus vier Buchstaben für "Once And Only Once", das wiederum ein Prinzip in der eXtreme-Programmierung ist, das eine mehrseitige Erklärung und Diskussion) enthält im Wiki .

Die OAOO-Wiki-Seite enthält ein sehr interessantes Zitat von Ron Jeffries:

Ich habe einmal gesehen, wie Beck zwei Patches mit fast völlig unterschiedlichem Code als "Duplizierung" deklarierte, sie so änderte, dass sie Duplikate waren, und dann die neu eingefügte Duplizierung entfernte, um etwas offensichtlich Besseres zu finden.

Worauf er näher eingeht:

Ich erinnere mich, dass Beck einmal zwei Schleifen gesehen hat, die ziemlich unterschiedlich waren: Sie hatten unterschiedliche Strukturen und unterschiedliche Inhalte, was so gut wie nichts Dupliziertes ist, außer dem Wort "für", und die Tatsache, dass sie - unterschiedlich - über dieselbe Schleife gingen Sammlung.

Er änderte die zweite Schleife auf die gleiche Weise wie die erste. Dies erforderte das Ändern des Hauptteils der Schleife, um die Elemente gegen Ende der Sammlung zu überspringen, da in der vorherigen Version nur die Vorderseite der Sammlung ausgeführt wurde. Jetzt waren die for-Anweisungen dieselben. "Nun, ich muss diese Duplizierung beseitigen, sagte er und verschob den zweiten Körper in die erste Schleife und löschte die zweite Schleife vollständig.

Jetzt hatte er zwei Arten ähnlicher Verarbeitung in einer Schleife. Er fand dort eine Art Vervielfältigung, extrahierte eine Methode, tat ein paar andere Dinge und voila! Der Code war viel besser.

Dieser erste Schritt - das Erstellen von Duplikaten - war verblüffend.

Dies zeigt: Sie können ohne doppelten Code duplizieren!

Und das Buch zeigt die Kehrseite der Medaille:

Im Rahmen Ihrer Online-Weinbestellanwendung erfassen und validieren Sie das Alter Ihres Benutzers sowie die Menge, die er bestellt. Laut dem Websitebesitzer sollten beide Zahlen und beide größer als Null sein. Sie codieren also die Validierungen:

def validate_age(value):
 validate_type(value, :integer)
 validate_min_integer(value, 0)

def validate_quantity(value):
 validate_type(value, :integer)
 validate_min_integer(value, 0)

Während der Codeüberprüfung springt der allwissende Bewohner diesen Code zurück und behauptet, es handele sich um eine DRY-Verletzung: Beide Funktionskörper sind gleich.

Sie liegen falsch. Der Code ist der gleiche, aber das Wissen, das sie darstellen, ist unterschiedlich. Die beiden Funktionen validieren zwei separate Dinge, die zufällig dieselben Regeln haben. Das ist ein Zufall, keine Vervielfältigung.

Dies ist duplizierter Code, bei dem es sich nicht um doppelte Kenntnisse handelt.

Es gibt eine großartige Anekdote über die Vervielfältigung, die zu einem tiefen Einblick in die Natur der Programmiersprachen führt: Viele Programmierer kennen die Programmiersprache Schema und dass es sich um eine prozedurale Sprache in der LISP-Familie handelt. Klassen- und übergeordnete Verfahren, lexikalisches Scoping, lexikalische Abschlüsse und ein Fokus auf rein funktionale, referenziell transparente Code- und Datenstrukturen. Was jedoch nicht viele Menschen wissen, ist, dass es geschaffen wurde, um objektorientierte Programmierung und Akteursysteme zu studieren (die von den Autoren des Schemas als eng verwandt angesehen wurden, wenn nicht dasselbe).

Zwei der grundlegenden Prozeduren in Schema sind lambda, das eine Prozedur erstellt, und apply, das eine Prozedur ausführt. Die Ersteller von Scheme haben zwei weitere hinzugefügt: alpha, wodurch ein a ctor (oder Objekt) erstellt wird, und send , die eine Nachricht an einen Akteur (oder ein Objekt) sendet.

Eine ärgerliche Folge von apply und send war, dass die elegante Syntax für Prozeduraufrufe nicht mehr funktionierte. In Schema, wie wir es heute kennen (und in so ziemlich jedem LISP), wird eine einfache Liste normalerweise so interpretiert, dass "das erste Element der Liste als Prozedur interpretiert und apply für den Rest der Liste interpretiert wird als Argumente ". Sie können also schreiben

(+ 2 3)

und das ist gleichbedeutend mit

(apply '+ '(2 3))

(Oder etwas in der Nähe, mein Schema ist ziemlich verrostet.)

Dies funktioniert jedoch nicht mehr, da Sie nicht wissen, ob Sie apply oder send verwenden sollen (vorausgesetzt, Sie möchten keine der beiden Prioritäten setzen, die die Ersteller von Scheme nicht festgelegt haben Sie wollten, dass beide Paradigmen gleich sind. … Oder tust du? Die Ersteller von Scheme haben erkannt, dass sie tatsächlich nur nach dem Typ des Objekts suchen müssen, auf das das Symbol verweist: Wenn + Eine Prozedur ist, apply, wenn + Ist ein Schauspieler, Sie send eine Nachricht an ihn. Sie brauchen eigentlich keine separaten apply und send, Sie können so etwas wie apply-or-send Haben.

Und genau das haben sie getan: Sie haben den Code der beiden Prozeduren apply und send genommen und sie als zwei Zweige einer Bedingung in dieselbe Prozedur eingefügt.

Kurz darauf schrieben sie auch den Scheme-Interpreter, der bis zu diesem Zeitpunkt in einer Assemblersprache für eine Registermaschine auf sehr niedriger Ebene geschrieben war, in einem Scheme auf hoher Ebene neu. Und sie bemerkten etwas Erstaunliches: Der Code in den beiden Zweigen der Bedingung wurde identisch. Sie hatten dies vorher nicht bemerkt: Die beiden Prozeduren wurden zu unterschiedlichen Zeiten geschrieben (sie begannen mit einem "minimalen LISP" und fügten dann OO dazu) sowie die Ausführlichkeit und Niedrigkeit hinzu der Versammlung bedeutete, dass sie tatsächlich ziemlich unterschiedlich geschrieben waren, aber nachdem sie in einer Hochsprache neu geschrieben worden waren, wurde klar, dass sie dasselbe taten.

Dies führte zu einem tiefgreifenden Verständnis von Actors und OO: Ausführen eines objektorientierten Programms und Ausführen eines Programms in einer prozeduralen Sprache mit lexikalischen Abschlüssen und richtigen Tail-Aufrufen, sind dasselbe. Der einzige Unterschied besteht darin, ob die Grundelemente Ihrer Sprache Objekte/Akteure oder Prozeduren sind. Aber operativ, es ist das gleiche.

Dies führt auch zu einer weiteren wichtigen Erkenntnis, die bis heute leider nicht gut verstanden wird: Sie können die objektorientierte Abstraktion nicht ohne richtige Tail-Aufrufe aufrechterhalten oder aggressiver formulieren: eine Sprache, die behauptet, objektorientiert zu sein, aber keine richtigen Tail-Aufrufe hat , ist nicht objektorientiert. (Leider gilt das für alle meine Lieblingssprachen, und es ist nicht akademisch: Ich habe bin auf dieses Problem gestoßen, dass ich die Kapselung unterbrechen musste, um einen Stapelüberlauf zu vermeiden.)

Dies ist ein Beispiel, bei dem eine sehr gut versteckte Vervielfältigung tatsächlich verdeckt ein wichtiges Stück Wissen und entdecken diese Vervielfältigung auch Wissen enthüllte.

39
Jörg W Mittag

Wählen Sie im Zweifelsfall immer die einfachste Lösung, die das Problem löst.

Wenn sich herausstellt, dass die einfache Lösung zu einfach war, kann sie leicht geändert werden. Eine zu komplexe Lösung ist andererseits auch schwieriger und riskanter zu ändern.

KISS ist wirklich das wichtigste aller Designprinzipien, wird aber oft übersehen, da unsere Entwicklerkultur großen Wert darauf legt, klug zu sein und ausgefallene Techniken einzusetzen. Aber manchmal ist ein if wirklich besser als ein Strategiemuster .

Das Prinzip DRY] zwingt die Programmierer manchmal dazu, komplexe, schwer zu wartende Funktionen/Klassen zu schreiben.

Halt genau dort an! Der Zweck des DRY -Prinzips besteht darin, einen besser wartbaren Code zu erhalten. Wenn die Anwendung des Prinzips in einem bestimmten Fall dazu führen würde auf weniger wartbaren Code, dann sollte das Prinzip nicht angewendet werden.

Denken Sie daran, dass keines dieser Prinzipien Ziele für sich sind. Das Ziel ist es, Software zu entwickeln, die ihren Zweck erfüllt und die bei Bedarf angepasst und erweitert werden kann. Sowohl KISS, DRY, SOLID als auch alle anderen Prinzipien sind Mittel , um dieses Ziel zu erreichen. Aber alle haben ihre Grenzen und können so angewendet werden, dass sie dem Endziel entgegenwirken, funktionierende und wartbare Software zu schreiben.

8
JacquesB

IMHO: Wenn Sie aufhören, sich auf den Code KISS/DRY zu konzentrieren, und sich auf die Anforderungen konzentrieren, die den Code steuern, finden Sie die bessere Antwort, die Sie suchen.

Ich glaube:

  1. Wir müssen uns gegenseitig ermutigen, pragmatisch zu bleiben (wie Sie es tun)

  2. Wir dürfen niemals aufhören, die Wichtigkeit von Tests zu fördern

  3. Wenn Sie sich mehr auf die Anforderungen konzentrieren, werden Ihre Fragen gelöst.

TLDR

Wenn Sie Teile unabhängig voneinander ändern möchten, halten Sie die Funktionen unabhängig, indem Sie keine Hilfsfunktionen haben. Wenn Ihre Anforderungen (und zukünftige Änderungen daran) für alle Funktionen gleich sind, verschieben Sie diese Logik in eine Hilfsfunktion.

Ich denke, alle unsere bisherigen Antworten ergeben ein Venn-Diagramm: Wir sagen alle dasselbe, aber wir geben Details zu verschiedenen Teilen.

Außerdem erwähnte niemand anderes das Testen, weshalb ich diese Antwort teilweise schrieb. Ich denke, wenn jemand erwähnt, dass Programmierer Angst haben, Änderungen vorzunehmen, dann ist es sehr unklug, nicht über das Testen zu sprechen! Selbst wenn wir "denken", dass das Problem im Code liegt, könnte es das eigentliche Problem sein, dass keine Tests durchgeführt werden. Objektiv überlegene Entscheidungen werden realistischer, wenn Menschen zuerst in automatisierte Tests investiert haben.

Erstens ist es Weisheit, Angst zu vermeiden - gute Arbeit!

Hier ist ein Satz, den Sie gesagt haben: Die Programmierer haben große Angst, Änderungen an solchen [Hilfs-] Funktionen vorzunehmen, oder sie verursachen Fehler in anderen Anwendungsfällen der Funktion

Ich bin damit einverstanden, dass diese Angst der Feind ist, und Sie dürfen niemals an Prinzipien festhalten, wenn sie nur Angst vor kaskadierenden Fehlern/Arbeiten/Veränderungen verursachen. Wenn das Kopieren/Einfügen zwischen mehreren Funktionen die einzige Möglichkeit ist, diese Angst zu beseitigen (was ich nicht glaube - siehe unten), sollten Sie dies tun.

Die Tatsache, dass Sie diese Angst vor Änderungen spüren und versuchen, etwas dagegen zu unternehmen, macht Sie zu einem besseren Fachmann als viele andere, denen die Verbesserung des Codes nicht wichtig genug ist - sie tun einfach das, was ihnen gesagt wurde und nehmen Sie die minimalen Änderungen vor, um das Ticket zu schließen.

Außerdem (und ich kann sagen, dass ich wiederhole, was Sie bereits wissen): People Skills trumpfen Design Skills. Wenn die realen Menschen in Ihrem Unternehmen absolut schlecht sind, spielt es keine Rolle, ob Ihre "Theorie" besser ist. Möglicherweise müssen Sie Entscheidungen treffen, die objektiv schlechter sind, aber Sie wissen, dass die Leute, die sie aufrechterhalten, in der Lage sind, zu verstehen und mit ihnen zu arbeiten. Viele von uns verstehen auch das Management, das uns (IMO) im Mikromanagement verwaltet, und finden Wege, um das erforderliche Refactoring immer abzulehnen.

Als jemand, der ein Anbieter ist, der Code für Kunden schreibt, muss ich die ganze Zeit daran denken. Ich möchte vielleicht Currying und Metaprogrammierung verwenden, weil es ein Argument gibt, dass es objektiv besser ist, aber im wirklichen Leben sehe ich Leute, die durch diesen Code verwirrt sind, weil es nicht visuell offensichtlich ist Was ist los.

Zweitens löst besseres Testen mehrere Probleme gleichzeitig

Wenn (und nur wenn) Sie effektive, stabile und bewährte automatisierte Tests (Einheit und/oder Integration) haben, werden Sie bestimmt feststellen, dass die Angst nachlässt. Für Neulinge in automatisierten Tests kann es sehr beängstigend sein, den automatisierten Tests zu vertrauen. Neulinge können all diese grünen Punkte sehen und haben sehr wenig Vertrauen, dass diese grünen Punkte die reale Produktionsarbeit widerspiegeln. Wenn Sie jedoch persönlich Vertrauen in die automatisierten Tests haben, können Sie andere emotional/relational dazu ermutigen, ihm ebenfalls zu vertrauen.

Für Sie (falls Sie dies noch nicht getan haben) besteht der erste Schritt darin, Testpraktiken zu untersuchen, wenn Sie dies nicht getan haben. Ich gehe ehrlich davon aus, dass Sie dieses Zeug bereits kennen, aber da ich es in Ihrem ursprünglichen Beitrag nicht erwähnt habe, muss ich darüber sprechen. Weil automatisierte Tests so wichtig und relevant für Ihre Situation sind, die Sie gestellt haben.

Ich werde hier nicht versuchen, alle Testpraktiken in einem einzigen Beitrag im Alleingang zusammenzufassen, aber ich möchte Sie herausfordern, sich auf die Idee von "refaktorsicheren" Tests zu konzentrieren. Bevor Sie einen Unit-/Integrationstest für den Code ausführen, fragen Sie sich, ob es gültige Möglichkeiten gibt, den CUT (zu testenden Code) zu überarbeiten, der den soeben geschriebenen Test unterbrechen würde. Wenn das stimmt, löschen Sie (IMO) diesen Test. Es ist besser, weniger automatisierte Tests zu haben, die beim Refactor nicht unnötig unterbrochen werden, als wenn Ihnen etwas sagt, dass Sie eine hohe Testabdeckung haben (Qualität über Quantität). Das Refactoring zu vereinfachen ist (IMO) der Hauptzweck automatisierter Tests.

Da ich diese "refaktorsichere" Philosophie im Laufe der Zeit übernommen habe, bin ich zu folgenden Schlussfolgerungen gekommen:

  1. Automatisierte Integrationstests sind besser als Komponententests
  2. Schreiben Sie für Integrationstests bei Bedarf "Simulatoren/Fälschungen" mit "Vertragstests".
  3. Testen Sie niemals eine private API - seien es Methoden privater Klassen oder nicht exportierte Funktionen aus einer Datei.

Verweise:

Während Sie nach Testpraktiken suchen, müssen Sie möglicherweise zusätzliche Zeit aufwenden, um diese Tests selbst zu schreiben. Manchmal ist der einzig beste Ansatz, niemandem zu sagen, dass Sie das tun, weil er Sie mikromanagt. Offensichtlich ist dies nicht immer möglich, da der Testbedarf möglicherweise größer ist als der Bedarf an einer guten Work-Life-Balance. Aber manchmal gibt es Dinge, die klein genug sind, um eine Aufgabe heimlich um ein oder zwei Tage zu verzögern, um nur die benötigten Tests/Codes zu schreiben. Ich weiß, dass dies eine kontroverse Aussage sein kann, aber ich denke, dass es Realität ist.

Darüber hinaus können Sie natürlich so politisch umsichtig wie möglich sein, um andere zu ermutigen, Schritte zu unternehmen, um Tests selbst zu verstehen/zu schreiben. Oder vielleicht sind Sie der technische Leiter, der eine neue Regel für Codeüberprüfungen auferlegen kann.

Wenn Sie mit Ihren Kollegen über das Testen sprechen, erinnert uns Punkt 1 (pragmatisch) hoffentlich alle daran, zuerst zuzuhören und nicht aufdringlich zu werden.

Drittens konzentrieren Sie sich auf die Anforderungen, nicht auf den Kodex

Zu oft konzentrieren wir uns auf unseren Code und verstehen das Gesamtbild, das unser Code lösen soll, nicht genau! Manchmal muss man aufhören zu streiten, ob der Code sauber ist, und sicherstellen, dass man die Anforderungen, die den Code steuern sollen, gut versteht.

Es ist wichtiger, dass Sie das Richtige tun, als dass Sie das Gefühl haben, dass Ihr Code nach Ideen wie KISS/DRY "hübsch" ist. Deshalb zögere ich, mich um diese Schlagworte zu kümmern, weil sie (in der Praxis) Sie versehentlich dazu bringen, sich auf Ihren Code zu konzentrieren, ohne darüber nachzudenken, dass die Anforderungen a liefern gutes Urteil über gute Codequalität.


Wenn die Anforderungen von zwei Funktionen voneinander abhängig/gleich sind, fügen Sie die Implementierungslogik dieser Anforderung in eine Hilfsfunktion ein. Die Eingaben für diese Hilfsfunktion sind die Eingaben für die Geschäftslogik für diese Anforderung.

Wenn die Anforderungen der Funktionen unterschiedlich sind, kopieren Sie sie zwischen ihnen. Wenn beide in diesem Moment zufällig denselben Code haben, aber zu Recht unabhängig voneinander geändert werden kann, ist eine Hilfsfunktion schlecht , weil dies der Fall ist Beeinflussen einer anderen Funktion, deren Anforderung darin besteht, sich unabhängig zu ändern.

Beispiel 1: Sie haben eine Funktion namens "getReportForCustomerX" und "getReportForCustomerY", und beide fragen die Datenbank auf dieselbe Weise ab. Stellen wir uns auch vor, es gibt eine Geschäftsanforderung, bei der jeder Kunde seinen Bericht buchstäblich nach seinen Wünschen anpassen kann. In diesem Fall möchten die Kunden aufgrund ihres Designs unterschiedliche Nummern in ihrem Bericht. Wenn Sie also einen neuen Kunden Z haben, der einen Bericht benötigt, ist es möglicherweise am besten, die Abfrage eines anderen Kunden zu kopieren/einzufügen, den Code festzuschreiben und einen zu verschieben. Selbst wenn die Abfragen genau gleich sind, besteht der Definitionspunkt dieser Funktionen darin, Änderungen von einem Kunden zu trennen, der sich auf einen anderen auswirkt. In den Fällen, in denen Sie eine neue Funktion bereitstellen, die alle Kunden in ihrem Bericht wünschen, dann ja: Möglicherweise geben Sie die gleichen Änderungen zwischen allen Funktionen ein.

Nehmen wir jedoch an, wir beschließen, eine Hilfsfunktion namens queryData zu erstellen. Der Grund dafür ist, dass durch die Einführung einer Hilfsfunktion mehr kaskadierende Änderungen auftreten. Wenn Ihre Abfrage eine "where" -Klausel enthält, die für alle Kunden gleich ist. Sobald ein Kunde möchte, dass ein Feld für ihn unterschiedlich ist, müssen Sie statt 1) ​​die Abfrage in Funktion X ändern, 1 ) Ändern Sie die Abfrage, um das zu tun, was Kunde X möchte. 2) Fügen Sie der Abfrage Bedingungen hinzu, um dies nicht für andere zu tun. Das Hinzufügen weiterer Bedingungen zu einer Abfrage ist logisch anders. Ich weiß vielleicht, wie man einen Unterabschnitt zu einer Abfrage hinzufügt, aber das bedeutet nicht, dass ich weiß, wie man diesen Unterabschnitt abhängig macht, ohne die Leistung für diejenigen zu beeinträchtigen, die ihn nicht verwenden.

Sie stellen also fest, dass für die Verwendung einer Hilfsfunktion zwei Änderungen anstelle von einer erforderlich sind. Ich weiß, dass dies ein erfundenes Beispiel ist, aber die boolesche Komplexität, die beibehalten werden muss, wächst meiner Erfahrung nach mehr als linear. Daher zählt das Hinzufügen von Bedingungen als "eine weitere Sache", um die sich die Leute kümmern müssen, und als "eine weitere Sache", die jedes Mal aktualisiert werden muss.

Dieses Beispiel scheint mir die Situation zu sein, in die Sie geraten. Einige Menschen schrecken emotional vor der Idee zurück, zwischen diesen Funktionen zu kopieren/einzufügen, und eine solche emotionale Reaktion ist in Ordnung. Das Prinzip "Minimieren von kaskadierenden Änderungen" erkennt jedoch objektiv die Ausnahmen, wenn das Kopieren/Einfügen in Ordnung ist.

Beispiel 2: Sie haben drei verschiedene Kunden, aber das einzige, was Sie zwischen ihren Berichten unterscheiden dürfen, sind die Titel der Spalten. Beachten Sie, dass diese Situation sehr unterschiedlich ist. Unsere Geschäftsanforderung besteht nicht mehr darin, "dem Kunden einen Mehrwert zu bieten, indem im Wettbewerb Flexibilität im Bericht ermöglicht wird". Stattdessen lautet die Geschäftsanforderung "Vermeiden Sie übermäßige Arbeit, indem Sie den Kunden nicht erlauben, den Bericht stark anzupassen". In dieser Situation würden Sie die Abfragelogik nur dann ändern, wenn Sie auch sicherstellen müssen, dass jeder andere Kunde dieselbe Änderung erhält. In diesem Fall möchten Sie auf jeden Fall eine Hilfsfunktion mit einem Array als Eingabe erstellen - wie lauten die "Titel" für die Spalten?.

Wenn Produktbesitzer in Zukunft entscheiden, dass sie Kunden erlauben möchten, etwas an der Abfrage anzupassen, werden Sie der Hilfsfunktion weitere Flags hinzufügen.

Fazit

Je mehr Sie sich auf die Anforderungen anstatt auf den Code konzentrieren, desto isomorpher ist der Code zu den wörtlichen Anforderungen. Sie schreiben natürlich besseren Code.

4
Alexander Bird

Versuchen Sie, einen vernünftigen Mittelweg zu finden. Teilen Sie eine Funktion mit vielen Parametern und komplexen Bedingungen in einige einfachere Funktionen auf. Es wird einige Wiederholungen bei den Anrufern geben, aber nicht so sehr, als hätten Sie den allgemeinen Code überhaupt nicht in Funktionen verschoben.

Ich bin kürzlich mit Code darauf gestoßen, an dem ich arbeite, um eine Schnittstelle zu Google- und iTunes-App-Stores herzustellen. Ein Großteil des allgemeinen Ablaufs ist der gleiche, aber es gibt genug Unterschiede, so dass ich nicht einfach eine Funktion schreiben konnte, um alles zu kapseln.

Der Code ist also wie folgt aufgebaut:

Google::validate_receipt(...)
    f1(...)
    f2(...)
    some google-specific code
    f3(...)

iTunes::validate_receipt(...)
    some iTunes-specific code
    f1(...)
    f2(...)
    more iTunes-specific code
    f3(...)

Ich bin nicht allzu besorgt, dass das Aufrufen von f1 () und f2 () in beiden Validierungsfunktionen gegen das Prinzip DRY] verstößt, da das Kombinieren diese komplizierter machen und keine einzige, genau definierte Funktion ausführen würde Aufgabe.

3
Barmar

Kent Beck vertrat 4 Regeln des einfachen Designs, die sich auf diese Frage beziehen. Wie von Martin Fowler formuliert, sind sie:

  • Besteht die Tests
  • Enthüllt die Absicht
  • Keine Vervielfältigung
  • Wenigste Elemente

Es gibt viele Diskussionen über die Reihenfolge der beiden mittleren, daher kann es sich lohnen, sie als gleich wichtig zu betrachten.

DRY ist das dritte Element in der Liste, und KISS könnte als eine Kombination aus dem 2. und 4. oder sogar der gesamten Liste zusammen betrachtet werden.

Diese Liste bietet eine alternative Ansicht zur Dichotomie DRY/KISS. Zeigt Ihr DRY Code die Absicht an? Zeigt Ihr KISS Code?) Können Sie die Ether-Version aufschlussreicher oder weniger dupliziert machen?

Das Ziel ist nicht DRY oder KISS, es ist guter Code. DRY, KISS und diese Regeln sind nur Werkzeuge, um dorthin zu gelangen.

3
Blaise Pascal