Ist dies ein gutes Muster: Ersetzen einer langen Funktion durch eine Reihe von Lambdas?

14

Ich bin kürzlich auf die folgende Situation gestoßen.

class A{
public:
    void calculate(T inputs);
}

Erstens Astellt es ein Objekt in der physischen Welt dar, was ein starkes Argument dafür ist, die Klasse nicht aufzuteilen. Nun calculate()stellt sich heraus, dass es eine ziemlich lange und komplizierte Funktion ist. Ich sehe drei mögliche Strukturen dafür:

  • Schreiben Sie es als Textwand - Vorteile - alle Informationen sind an einem Ort
  • Schreiben Sie privateHilfsfunktionen in die Klasse und verwenden Sie sie im calculateHauptteil - Nachteile - der Rest der Klasse kennt / kümmert sich / versteht nicht um diese Methoden
  • schreibe wie calculatefolgt:

    void A::calculate(T inputs){    
        auto lambda1 = () [] {};    
        auto lambda2 = () [] {};    
        auto lambda3 = () [] {};
    
        lambda1(inputs.first_logical_chunk);
        lambda2(inputs.second_logical_chunk);
        lambda3(inputs.third_logical_chunk);
    }
    

Kann dies als eine gute oder schlechte Praxis angesehen werden? Zeigt dieser Ansatz irgendwelche Probleme? Alles in allem sollte ich dies als einen guten Ansatz betrachten, wenn ich wieder mit der gleichen Situation konfrontiert bin?


BEARBEITEN:

class A{
    ...
public:
    // Reconfiguration of the algorithm.
    void set_colour(double colour);
    void set_density(double density);
    void set_predelay(unsigned long microseconds);
    void set_reverb_time(double reverb_time, double room_size);
    void set_drywet(double left, double right);
    void set_room_size(double value);;

private:
    // Sub-model objects.
    ...
}

Alle diese Methoden:

  • einen Wert bekommen
  • Berechnen Sie einige andere Werte, ohne state zu verwenden
  • Rufen Sie einige der "Submodell-Objekte" auf, um ihren Status zu ändern.

Mit Ausnahme set_room_size()dieser Methoden wird der angeforderte Wert einfach an Unterobjekte übergeben. set_room_size()Auf der anderen Seite werden einige Bildschirme mit undurchsichtigen Formeln angezeigt, und dann wird (2) ein halber Bildschirm zum Aufrufen von Unterobjektsetzern ausgeführt, um die verschiedenen erhaltenen Ergebnisse anzuwenden. Deshalb habe ich die Funktion in zwei Lambdas aufgeteilt und am Ende der Funktion aufgerufen. Hätte ich es in logischere Stücke aufteilen können, hätte ich mehr Lambdas isoliert.

Unabhängig davon besteht das Ziel der aktuellen Frage darin, festzustellen, ob diese Denkweise fortbestehen soll oder ob sie allenfalls keinen Mehrwert bringt (Lesbarkeit, Wartbarkeit, Debug-Fähigkeit usw.).

Vorac
quelle
2
Was glauben Sie, was Funktionsaufrufe mit Lambdas nicht bringen werden?
Blrfl
1
Firstly, A represents an object in the physical world, which is a strong argument for not splitting the class up.Sicherlich Astellt Daten über ein Objekt , das existieren könnte in der physischen Welt. Sie können eine Instanz von Aohne das reale Objekt und ein reales Objekt ohne eine Instanz von haben A, so dass es unsinnig ist, sie so zu behandeln, als wären sie ein und dasselbe.
Doval
@Blrfl, Kapselung - niemand außer calculate()Ihnen kennt diese Unterfunktionen.
Vorac
Wenn all diese Berechnungen für nur relevant Asind, ist das ein bisschen extrem.
Blrfl
1
"Erstens Astellt es ein Objekt in der physischen Welt dar, was ein starkes Argument dafür ist, die Klasse nicht aufzuteilen." Dies wurde mir leider gesagt, als ich anfing zu programmieren. Es hat Jahre gedauert, bis mir klar wurde, dass es ein Haufen Eishockey ist. Es ist ein schrecklicher Grund, Dinge zu gruppieren. Ich kann nicht artikulieren , was sind gute Gründe für die Gruppe Dinge (zumindest zu meiner Zufriedenheit), aber das ist eine Sie jetzt verwerfen sollte. Das Ende von "gutem Code" ist, dass er richtig funktioniert, relativ leicht zu verstehen und relativ leicht zu ändern ist (dh Änderungen haben keine seltsamen Nebenwirkungen).
jpmc26

Antworten:

13

Nein, dies ist im Allgemeinen kein gutes Muster

Sie zerlegen eine Funktion mit Lambda in kleinere Funktionen. Es gibt jedoch ein viel besseres Werkzeug, um Funktionen aufzubrechen: Funktionen.

Lambdas funktionieren, wie Sie gesehen haben, aber sie bedeuten viel, viel, viel mehr als nur die Aufteilung einer Funktion in lokale Teile . Lambdas machen:

  • Verschlüsse. Sie können Variablen im äußeren Bereich innerhalb des Lambda verwenden. Das ist sehr mächtig und sehr kompliziert.
  • Neuzuweisung. Während Ihr Beispiel die Zuweisung erschwert, muss man immer darauf achten, dass Code jederzeit Funktionen austauschen kann.
  • Erstklassige Funktionen. Sie können eine Lambda-Funktion an eine andere Funktion übergeben, indem Sie etwas tun, das als "funktionale Programmierung" bezeichnet wird.

In dem Moment, in dem Sie Lambdas in den Mix aufnehmen, muss der nächste Entwickler, der sich den Code ansieht, alle diese Regeln sofort mental laden, um zu sehen, wie Ihr Code funktioniert. Sie wissen nicht, dass Sie nicht alle diese Funktionen nutzen werden. Dies ist im Vergleich zu den Alternativen sehr teuer.

Es ist wie mit einer Hinterhacke, um Ihre Gartenarbeit zu tun. Sie wissen, dass Sie damit nur kleine Löcher für die diesjährigen Blumen graben, aber die Nachbarn werden nervös.

Bedenken Sie, dass Sie Ihren Quellcode nur visuell gruppieren müssen. Dem Compiler ist es eigentlich egal, dass Sie Dinge mit Lambdas belegen. Tatsächlich würde ich erwarten, dass der Optimierer alles, was Sie gerade beim Kompilieren getan haben, sofort rückgängig macht. Sie sind nur für den nächsten Leser da (Vielen Dank, auch wenn wir uns in Bezug auf die Methodik nicht einig sind! Code wird weitaus häufiger gelesen als geschrieben!). Alles, was Sie tun, ist die Gruppierungsfunktionalität.

  • Funktionen, die lokal innerhalb des Quellcode-Streams platziert sind, würden genauso gut funktionieren, ohne Lambda aufzurufen. Auch hier ist alles, was zählt, dass der Leser es lesen kann.
  • Die Kommentare oben in der Funktion lauten "Wir teilen diese Funktion in drei Teile", gefolgt von langen Zeilen // ------------------zwischen den einzelnen Teilen.
  • Sie können auch jeden Teil der Berechnung in einen eigenen Bereich einordnen. Dies hat den Vorteil, dass zweifelsohne sofort bewiesen wird, dass es keine variable Aufteilung zwischen den Teilen gibt.

BEARBEITEN: Nachdem ich Ihre Bearbeitung mit Beispielcode gesehen habe, neige ich dazu, dass die Kommentarnotation die sauberste ist und Klammern verwendet werden, um die in den Kommentaren angegebenen Grenzen durchzusetzen. Wenn jedoch eine der Funktionen in anderen Funktionen wiederverwendet werden kann, würde ich empfehlen, stattdessen Funktionen zu verwenden

void A::set_room_size(double value)
{
    {
        // Part 1: {description of part 1}
        ...
    }
    // ------------------------
    {
        // Part 2: {description of part 2}
        ...
    }
    // ------------------------
    {
        // Part 3: {description of part 3}
        ...
    }
}
Cort Ammon - Setzen Sie Monica wieder ein
quelle
Es ist also keine objektive Zahl, aber ich würde subjektiv behaupten, dass ich allein durch das Vorhandensein von Lambda-Funktionen jeder Codezeile zehnmal mehr Aufmerksamkeit schenke, weil sie so viel Potenzial haben, gefährlich kompliziert, schnell und einfach zu werden eine unschuldig aussehende Codezeile (wie count++)
Cort Ammon - Reinstate Monica
Großartiger Punkt. Ich sehe dies als die wenigen Vorteile des Ansatzes mit Lambdas - (1) lokal benachbartem Code und mit lokalem Gültigkeitsbereich (dies würde durch Funktionen auf Dateiebene verloren gehen). (2) Der Compiler stellt sicher, dass keine lokalen Variablen zwischen Codesegmenten geteilt werden. So können diese Vorteile erhalten werden, indem calculate()in {}Blöcke aufgeteilt und gemeinsam genutzte Daten im calculate()Geltungsbereich deklariert werden . Ich dachte, wenn ich sehe, dass die Lambdas nicht fangen, würde ein Leser nicht durch die Macht der Lambdas belastet.
Vorac,
"Ich dachte, dass ein Leser, wenn er sieht, dass die Lambdas nicht gefangen werden, nicht von der Macht der Lambdas belastet wird." Das ist eigentlich eine faire, aber umstrittene Aussage, die das Herz der Sprachwissenschaft berührt. Wörter haben normalerweise Konnotationen, die über ihre Bezeichnungen hinausgehen. Ob meine Konnotation lambdaunfair ist oder ob Sie die Leute grob dazu zwingen, der strengen Konnotation zu folgen, ist keine leicht zu beantwortende Frage. In der Tat kann es völlig akzeptabel sein, dass Sie in lambdaIhrem Unternehmen so vorgehen, und in meinem Unternehmen völlig inakzeptabel, und keiner von beiden muss sich wirklich irren!
Cort Ammon - Reinstate Monica
Meine Meinung zur Konnotation von Lambda ergibt sich aus der Tatsache, dass ich mit C ++ 03 aufgewachsen bin, nicht mit C + 11. Ich habe jahrelang eine Wertschätzung für die spezifischen Stellen entwickelt, an denen C ++ durch einen Mangel an lambda, wie der for_eachFunktion, verletzt wurde . Wenn ich also einen Fehler sehe lambda, der nicht zu einem dieser leicht auffindbaren Problemfälle passt, gehe ich zunächst davon aus, dass er wahrscheinlich für die funktionale Programmierung verwendet wird, da er ansonsten nicht benötigt wird. Für viele Entwickler ist funktionale Programmierung eine völlig andere Denkweise als prozedurale oder OO-Programmierung.
Cort Ammon - Reinstate Monica
Danke fürs Erklären. Ich bin ein Anfänger im Programmieren und bin jetzt froh, dass ich gefragt habe - bevor sich die Gewohnheit aufgebaut hat.
Vorac
20

Ich denke, Sie haben eine schlechte Annahme gemacht:

Erstens repräsentiert A ein Objekt in der physischen Welt, was ein starkes Argument dafür ist, die Klasse nicht aufzuteilen.

Ich bin damit nicht einverstanden. Wenn ich zum Beispiel eine Klasse hätte, die ein Auto repräsentiert, würde ich sie definitiv aufteilen wollen, weil ich sicher möchte, dass eine kleinere Klasse die Reifen repräsentiert.

Sie sollten diese Funktion in kleinere private Funktionen aufteilen. Wenn es wirklich vom anderen Teil der Klasse getrennt zu sein scheint, kann dies ein Zeichen dafür sein, dass die Klasse getrennt werden sollte. Natürlich ist es schwer zu sagen, ohne ein explizites Beispiel.

In diesem Fall sehe ich den Vorteil der Verwendung von Lambda-Funktionen nicht wirklich, da der Code dadurch nicht wirklich sauberer wird. Sie wurden entwickelt, um die Programmierung funktionaler Stile zu unterstützen, aber das ist nicht das Gegenteil.

Was Sie geschrieben haben, ähnelt ein wenig geschachtelten Funktionsobjekten im Javascript-Stil. Was wiederum ein Zeichen dafür ist, dass sie eng zusammengehören. Sind Sie sicher, dass Sie keine separate Klasse für sie machen sollten?

Zusammenfassend denke ich nicht, dass dies ein gutes Muster ist.

AKTUALISIEREN

Wenn Sie keine Möglichkeit sehen, diese Funktionalität in einer aussagekräftigen Klasse zu kapseln, können Sie dateibezogene Hilfsfunktionen erstellen, die nicht zu Ihrer Klasse gehören. Dies ist schließlich C ++, OO-Design ist kein Muss.

Gábor Angyal
quelle
Klingt vernünftig, aber ich kann mir die Implementierung nur schwer vorstellen. Dateibereichsklasse mit statischen Methoden? Klasse, definiert in A(vielleicht sogar functor mit allen anderen Methoden private)? Klasse deklariert und definiert calculate()(dies sieht meinem Lambda-Beispiel sehr ähnlich). Zur Verdeutlichung: calculate()Gehört zu einer Familie von Methoden ( calculate_1(), calculate_2()usw.), von denen alle einfach sind, ist nur diese eine 2-Bildschirme-Formeln.
Vorac
@Vorac: Warum ist das calculate()so viel länger als bei allen anderen Methoden?
Kevin
@Vorac Es ist wirklich schwer zu helfen, ohne Ihren Code zu sehen. Kannst du es auch posten?
Gábor Angyal,
@ Kevin, die Formeln für alles sind in den Anforderungen angegeben. Code gepostet.
Vorac
4
Ich würde das 100 Mal verbessern, wenn ich könnte. "Modellieren von Objekten in der realen Welt" ist der Beginn der Todesspirale von Object Design. Es ist eine riesige rote Fahne in jedem Code.
Fred the Magic Wonder Dog