Ist die Verwendung verschachtelter Funktionsaufrufe eine schlechte Sache?

9

Bei einer kürzlich durchgeführten Hausaufgabe habe ich meine Funktionen auf hässliche Weise aufgerufen. uglyReceipt(cashParser(cashInput()))Das Programm selbst hat perfekt funktioniert, aber ich hatte immer noch das Gefühl, etwas falsch zu machen.

Ist das Aufrufen von Funktionen wie diese schlechte Praxis und wenn ja: Was soll ich stattdessen tun?

Carl Groth
quelle
1
Sind das verschachtelte Funktionen oder ist das ein Nest von Aufrufen von Funktionen? Wenn das letztere OP möglicherweise die funktionale Programmierung (neu) erfunden hat.
High Performance Mark
Was lässt Sie denken, dass dies eine schlechte Praxis wäre?
Ixrec
1
@gnat: Diese Frage hat nichts damit zu tun. Bei dieser Frage geht es um lexikalisch verschachtelte Funktionsdefinitionen. Bei dieser Frage geht es darum, das Ergebnis eines Funktionsaufrufs als Argument an einen anderen Funktionsaufruf zu übergeben.
Jörg W Mittag
Ich habe falsch verstanden - danke für den Hinweis auf @ JörgWMittag (zurückgezogene Abstimmung)
Mücke

Antworten:

11

Dies hängt wirklich davon ab, wie viel Verschachtelung Sie verwenden. Schließlich dürfen Sie Funktionsergebnisse direkt in Ausdrücken verwenden, um die Lesbarkeit zu verbessern. Sowohl Code, der keine verschachtelten Ausdrücke verwendet (wie Assembler-Code), als auch Code, der zu viele verschachtelte Ausdrücke verwendet, sind schwer zu lesen. Guter Code versucht, ein Gleichgewicht zwischen den Extremen herzustellen.

Schauen wir uns also einige Beispiele an. Der, den Sie in Ihrer Frage angegeben haben, scheint mir ziemlich legitim zu sein, also gibt es hier keinen Grund zur Sorge. Allerdings eine Linie wie

foo(bar(baz(moo, fab), bim(bam(ext, rel, woot, baz(moo, fab)), noob), bom, zak(bif)));

wäre definitiv nicht erträglich. Ebenso Code wie

double xsquare = x*x;
double ysquare = y*y;
double zsquare = z*z;
double xysquare = xsquare + ysquare;
double xyzsquare = xysquare + zsquare;
double length = sqrt(xyzsquare);

wäre auch nicht sehr lesbar. sqrt(x*x + y*y + z*z)ist viel einfacher zu verstehen, obwohl es insgesamt sechs verschiedene Operationen in einem Ausdruck kombiniert.

Mein Rat ist, darauf zu achten, welche Ausdrücke Sie noch leicht in Ihrem Kopf analysieren können. In dem Moment, in dem Sie einen zweiten Blick darauf werfen müssen, was ein einzelner Ausdruck bewirkt, ist es Zeit, eine zusätzliche Variable einzuführen.

cmaster - Monica wieder einsetzen
quelle
4

Ich denke, ob es gut oder schlecht ist, hängt stark vom Kontext ab. Der Hauptgrund, warum es als schlecht angesehen werden kann, ist, dass es (wohl) das Lesen und Debuggen des Codes erschwert. Dies gilt insbesondere dann, wenn Sie zum ersten Mal das Programmieren lernen. Wenn Ihre Codierungsfähigkeiten (und Ihr Code) weiterentwickelt werden, ist dies manchmal akzeptabel.

Betrachten Sie beispielsweise eine Fehlermeldung wie die folgende:

line 1492: missing argument "foo"

Wie Sie wissen, ob das fehlende Argument ist cashInput, cashParseroder uglyReceipt? Abhängig von der Sprache wird die Fehlermeldung möglicherweise angezeigt, jedoch möglicherweise nicht.

Wenn diese Funktionsaufrufe getrennt würden und die Fehlermeldung Sie immer noch auf Zeile 1492 verweist, würden Sie sofort wissen, wo das Problem liegt:

1491: input = cashInput()
1492: parsed_value = cashParser(input)
1493: receipt = uglyReceipt(parsed_value)

Da die Schritte separat aufgeteilt sind, ist das Debuggen viel einfacher, da bei jedem Schritt ein Haltepunkt festgelegt werden kann und Sie Werte einfach einfügen können, indem Sie den Wert der lokalen Variablen ändern.

Bryan Oakley
quelle
3

Das Ihrer Frage zugrunde liegende Konzept ist so wichtig, dass ich der Meinung bin, dass es eine andere Antwort als nur einen Kommentar benötigt (wie ich begonnen hatte).

Die anderen 3 Antworten bieten einige nützliche Überlegungen, ob eine bestimmte Situation die Verwendung von "verschachtelten Funktionsaufrufen" verdient. Aber vielleicht ist ein wichtigerer Punkt in den Kommentaren unter Ihrer Frage verborgen: Falls Sie die Subtilität der Vorschläge dieser gelehrten Leute verpasst haben, Carl, haben Sie selbst das Thema entdeckt, das eigentlich funktionale Programmierung heißt . Wenn Sie den Begriff noch nie gesehen haben, haben Sie vielleicht nicht gedacht, dass er in @ HighPerformanceMarks Kommentar wirklich ein "Ding" ist.

Aber tatsächlich ist es das! Über funktionale Programmierung wird seit Jahrzehnten geschrieben, seit John Hughes 'wegweisender Arbeit Why Functional Programming Matters . Es gibt einige Sprachen, die funktionale Sprachen sind (dh Sie können nur in einem funktionalen Programmierstil schreiben), Sprachen wie Erlang, Lisp, OCaml oder Haskell. Es gibt jedoch noch viel mehr Sprachen, die hybride imperative / funktionale Sprachen sind. Das heißt, sie sind traditionell zwingende Sprachen, bieten aber auch Unterstützung für die funktionale Programmierung, einschließlich Perl, C ++, Java, C # und vielen mehr. Der Wikipedia-Eintrag zur funktionalen Programmierung bietet einen schönen Abschnitt, der einen Vergleich des funktionalen Stils mit dem imperativen Stil für eine Reihe von Sprachen zeigt.

Zu den Unterschieden zwischen imperativen und funktionalen Stilen gibt es viel zu sagen, aber der wichtigste Ausgangspunkt ist, dass Funktionen oder Methoden bei der funktionalen Programmierung keine Nebenwirkungen haben , was das Verstehen und Debuggen von Programmen im Allgemeinen erleichtert.

Weitere Informationen finden Sie auch in Reginald Braithwaites Warum "Warum funktionale Programmierung wichtig ist" und einem weiteren interessanten Beitrag hier auf SO, Warum funktionale Sprachen?

Michael Sorens
quelle
2

Es ist absolut keine schlechte Praxis im Allgemeinen. Funktionen rufen Akzeptanzwerte auf und eine Möglichkeit, einen Wert zu erzeugen, besteht darin, eine andere Funktion aufzurufen.

Wenn ich sehe, dass eine Variable definiert wird, wie:

parsed_value = cashParser(input)

... Ich muss berücksichtigen, dass dies parsed_valuemöglicherweise mehrmals verwendet wird, und ich muss wahrscheinlich prüfen, ob dies zutrifft oder nicht (was ist, wenn meine Änderung an anderer Stelle etwas kaputt macht?). Wenn Sie weitere Variablen hinzufügen, kann es für den Leser komplexer werden, alle Variablen und ihre Beziehung zu verfolgen. Ich bin also erleichtert, wenn ich sehe:

receipt = uglyReceipt(cashParser(input))

... weil der Umfang / die Lebensdauer des Zwischenwerts offensichtlich ist. Wie immer kann es hilfreich sein, einen langen Ausdruck in separate Anweisungen aufzuteilen, insbesondere wenn ein Variablenname den Zweck eines Werts genauer beschreiben kann:

user_name = input()
Core-Dump
quelle