Interpretation des DRY-Prinzips


10

Im Moment kämpfe ich mit diesem Konzept von DRY (Don't Repeat Yourself) in meiner Codierung. Ich erstelle diese Funktion, in der ich befürchte, dass sie zu komplex wird, aber ich versuche, dem DRY-Prinzip zu folgen.

createTrajectoryFromPoint(A a,B b,C c,boolean doesSomething,boolean doesSomething2)

Diese Funktion benötigt 3 Eingabeparameter, und dann wird die Funktion angesichts der booleschen Kombinationen doesSomethingund etwas anderes tun doesSomething2. Das Problem, das ich habe, ist jedoch, dass diese Funktion mit jedem neuen booleschen Parameter, der hinzugefügt wird, an Komplexität zunimmt.

Meine Frage ist also, ob es besser ist, eine Reihe verschiedener Funktionen zu haben, die viel von derselben Logik haben (was gegen das DRY-Prinzip verstößt), oder eine Funktion, die sich bei einer Reihe von Parametern etwas anders verhält, sie aber viel komplexer macht (aber DRY erhalten)?


3
Kann die gemeinsame Logik in private Funktionen zerlegt werden, die createTrajectory...alle verschiedenen öffentlichen Funktionen aufrufen?
FrustratedWithFormsDesigner

Es könnte sein, aber diese privaten Funktionen müssten noch diese booleschen Parameter erhalten
Albinoswordfish

2
Ich denke, dies wäre / wird anhand eines konkreten Beispiels viel einfacher zu beantworten sein. Meine unmittelbare Reaktion ist, dass die Dichotomie, die Sie darstellen, nicht ganz real ist - dh dies sind nicht die einzigen beiden Möglichkeiten. Abgesehen davon würde ich jede Verwendung von a booleanals Parameter bestenfalls als verdächtig betrachten.
Jerry Coffin


Äh, warum zerlegen Sie die bedingten Dinge nicht in ihre eigenen Funktionen?
Rig

Antworten:


19

Boolesche Argumente zum Auslösen verschiedener Codepfade in einer einzelnen Funktion / Methode sind ein schrecklicher Codegeruch .

Was Sie tun, verstößt gegen die Grundsätze der losen Kopplung und des hohen Zusammenhalts sowie der Einzelverantwortung , die vorrangig viel wichtiger sind als DRY.

Das bedeutet, dass Dinge nur dann von anderen Dingen abhängen sollten, wenn sie müssen ( Kopplung ) und dass sie eine Sache und nur eine Sache (sehr gut) tun sollten ( Zusammenhalt ).

Durch Ihre eigene Unterlassung ist dies zu eng gekoppelt (alle booleschen Flags sind eine Art von Zustandsabhängigkeit, was eine der schlimmsten ist!) Und es sind zu viele individuelle Verantwortlichkeiten miteinander vermischt (zu komplex).

Was Sie tun, ist sowieso nicht im Sinne von DRY. Bei DRY geht es mehr um Wiederholung (das Rsteht für REPEAT). Das Vermeiden von Kopieren und Einfügen ist die grundlegendste Form. Was Sie tun, hat nichts mit Wiederholung zu tun.

Ihr Problem ist, dass Ihre Zerlegung Ihres Codes nicht auf der richtigen Ebene ist. Wenn Sie glauben, dass Sie doppelten Code haben, sollte dies eine eigene Funktion / Methode sein, die entsprechend parametrisiert, nicht kopiert und eingefügt wird, und die anderen sollten beschreibend benannt und an die Kernfunktion / -methode delegiert werden.


Ok, es scheint so zu sein, wie ich schreibe, ist nicht sehr gut (mein anfänglicher Verdacht). Ich bin mir nicht sicher, was dieser 'Sprit of DRY' ist
Albinoswordfish


4

Die Tatsache, dass Sie Boolesche Werte übergeben, damit die Funktion verschiedene Aufgaben ausführt, verstößt gegen das Prinzip der Einzelverantwortung. Eine Funktion sollte eines tun. Es sollte nur eines tun, und es sollte es gut machen.

Es riecht so, als müssten Sie es in mehrere kleinere Funktionen mit beschreibenden Namen aufteilen und die Codepfade trennen, die den Werten dieser Booleschen Werte entsprechen.

Sobald Sie dies getan haben, sollten Sie in den resultierenden Funktionen nach allgemeinem Code suchen und ihn in seine eigenen Funktionen zerlegen. Je nachdem, wie komplex diese Sache ist, können Sie möglicherweise sogar ein oder zwei Klassen herausrechnen.

Dies setzt natürlich voraus, dass Sie ein Versionskontrollsystem verwenden und über eine gute Testsuite verfügen, damit Sie eine Refactor-Funktion durchführen können, ohne befürchten zu müssen, dass etwas kaputt geht.


3

Warum erstellen Sie keine weitere Funktion, die die gesamte Logik Ihrer Funktion enthält, bevor Sie sich für etwas oder etwas entscheiden2 und dann drei Funktionen haben:

createTrajectoryFromPoint(A a,B b,C c){...}

dosomething(A a, B b, C c){...}

dosomething2(A a, B b, C c){...}

Wenn Sie nun drei gleiche Parametertypen an drei verschiedene Funktionen übergeben, wiederholen Sie sich erneut. Sie sollten also eine Struktur oder Klasse definieren, die A, B, C enthält.

Alternativ können Sie eine Klasse erstellen, die die Parameter A, B, C und eine Liste der auszuführenden Operationen enthält. Fügen Sie hinzu, welche Operationen (etwas, etwas2) mit diesen Parametern (A, B, C) ausgeführt werden sollen, indem Sie Operationen mit dem Objekt registrieren. Dann haben Sie eine Methode, um alle registrierten Operationen für Ihr Objekt aufzurufen.

public class MyComplexType
{
    public A a{get;set;}
    public B b{get;set;}
    public C c{get;set;}

    public delegate void Operation(A a, B b, C c);
    public List<Operation> Operations{get;set;}

    public MyComplexType(A a, B b, C c)
    {
        this.a = a;
        this.b = b;
        this.c = c   
        Operations = new List<Operation>();
    }

    public CallMyOperations()
    {
        foreach(var operation in Operations)
        {
            operation(a,b,c);
        }
    }
}

Um die möglichen Wertekombinationen für die Booleschen Werte dosomething und dosomething2 zu berücksichtigen, benötigen Sie zusätzlich zur Basisfunktion createTrajectoryFromPoint 4 Funktionen, nicht 2. Dieser Ansatz lässt sich nicht gut skalieren, da die Anzahl der Optionen zunimmt und selbst das Benennen der Funktionen mühsam wird.
JGWeissman

2

DRY kann zu weit gehen, es ist am besten, das Single-Responsibility-Prinzip (SRP) in Verbindung mit DRY zu verwenden. Das Hinzufügen von Bool-Flags zu einer Funktion, damit sie leicht unterschiedliche Versionen desselben Codes ausführt, kann ein Zeichen dafür sein, dass Sie mit einer Funktion zu viel tun. In diesem Fall würde ich vorschlagen, für jeden Fall, den Ihre Flags darstellen, eine separate Funktion zu erstellen. Wenn Sie dann jede Funktion ausgeschrieben haben, sollte es ziemlich offensichtlich sein, ob es einen gemeinsamen Abschnitt gibt, der in eine private Funktion verschoben werden kann, ohne alle Flags zu übergeben Wenn es keinen offensichtlichen Codeabschnitt gibt, wiederholen Sie sich wirklich nicht. Sie haben mehrere verschiedene, aber ähnliche Fälle.


1

Normalerweise gehe ich mit diesem Problem mehrere Schritte durch und höre auf, wenn ich nicht herausfinden kann, wie ich weiter gehen soll.

Tun Sie zuerst, was Sie getan haben. Gehen Sie hart mit DRY. Wenn Sie nicht mit einem großen haarigen Durcheinander enden, sind Sie fertig. Wenn Sie wie in Ihrem Fall keinen doppelten Code haben, aber jeder Boolesche Wert an 20 verschiedenen Stellen überprüft wird, fahren Sie mit dem nächsten Schritt fort.

Zweitens teilen Sie den Code in Blöcke. Die Booleschen Werte werden jeweils nur einmal (naja, manchmal auch zweimal) referenziert, um die Ausführung auf den richtigen Block zu lenken. Mit zwei Booleschen Werten erhalten Sie vier Blöcke. Jeder Block ist fast identisch. DRY ist weg. Machen Sie nicht jeden Block zu einer eigenen Methode. Das wäre eleganter, aber wenn Sie den gesamten Code in einer Methode zusammenfassen, kann jeder, der Wartungsarbeiten durchführt, einfacher oder sogar möglich erkennen, dass jede Änderung an vier Stellen vorgenommen werden muss. Mit gut organisiertem Code und einem großen Monitor werden die Unterschiede und Fehler fast offensichtlich sein. Sie haben jetzt wartbaren Code und er läuft schneller als das ursprüngliche Wirrwarr.

Drittens versuchen Sie, doppelte Codezeilen aus jedem Ihrer Blöcke zu holen und sie zu netten, einfachen Methoden zu machen. Manchmal kann man nichts machen. Manchmal kann man nicht viel machen. Aber jedes kleine bisschen, das Sie tun, bringt Sie zurück in Richtung DRY und macht den Code ein bisschen einfacher zu befolgen und sicherer zu pflegen. Im Idealfall enthält Ihre ursprüngliche Methode möglicherweise keinen doppelten Code. Zu diesem Zeitpunkt möchten Sie es möglicherweise ohne die booleschen Parameter in mehrere Methoden aufteilen oder nicht. Die Bequemlichkeit des aufrufenden Codes ist jetzt das Hauptanliegen.

Ich habe meine Antwort zu der großen Zahl bereits hier wegen des zweiten Schritts hinzugefügt. Ich hasse doppelten Code, aber wenn dies der einzig verständliche Weg ist, ein Problem zu lösen, tun Sie dies so, dass jeder auf einen Blick weiß, was Sie tun. Verwenden Sie mehrere Blöcke und nur eine Methode. Machen Sie die Blöcke in Namen, Abständen, Ausrichtungen usw. so identisch wie möglich. Die Unterschiede sollten dann beim Leser herausspringen. Es könnte offensichtlich machen, wie man es auf trockene Weise umschreibt, und wenn nicht, ist die Wartung ziemlich einfach.


0

Ein alternativer Ansatz besteht darin, die booleschen Parameter durch Schnittstellenparameter zu ersetzen, wobei Code zur Behandlung der verschiedenen booleschen Werte verwendet wird, die in die Schnittstellenimplementierungen umgestaltet wurden. Also hättest du

createTrajectoryFromPoint(A a,B b,C c,IX x,IY y)

Hier haben Sie Implementierungen von IX und IY, die die unterschiedlichen Werte für die Booleschen Werte darstellen. Im Körper der Funktion, wo immer Sie haben

if (doesSomething)
{
     ...
}
else
{
     ...
}

Sie ersetzen es durch einen Aufruf einer in IX definierten Methode, wobei die Implementierungen die ausgelassenen Codeblöcke enthalten.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.