Funktionen als Parameter an andere Funktionen übergeben, schlechte Praxis?

40

Wir haben gerade geändert, wie unsere AS3-Anwendung mit unserem Back-End kommuniziert, und wir sind dabei, ein REST-System zu implementieren, das unser altes ersetzt.

Leider befindet sich der Entwickler, der mit der Arbeit begonnen hat, jetzt im langfristigen Krankenstand und wurde mir übergeben. Ich arbeite seit ungefähr einer Woche damit und verstehe das System, aber eines hat mich beunruhigt. Es scheint viel Funktionsübergabe in Funktionen zu geben. Zum Beispiel übernimmt unsere Klasse, die den Aufruf an unsere Server vornimmt, eine Funktion, die dann ein Objekt aufruft und an dieses übergibt, wenn der Vorgang abgeschlossen ist und Fehler behandelt wurden usw.

Es gibt mir das "schlechte Gefühl", wo ich das Gefühl habe, dass es eine schreckliche Übung ist, und ich kann mir einige Gründe vorstellen, warum, aber ich möchte eine Bestätigung, bevor ich eine Überarbeitung des Systems vorschlage. Ich habe mich gefragt, ob jemand Erfahrung mit diesem möglichen Problem hat.

Elliot Blackburn
quelle
22
Warum fühlst du dich so? Haben Sie Erfahrung mit funktionaler Programmierung? (Ich nehme nicht an, aber Sie sollten einen Blick darauf werfen)
Phoshi
8
Dann halte das für eine Lektion! Was die Wissenschaft lehrt und was tatsächlich nützlich ist, hat oft überraschend wenig Überschneidungen. Es gibt eine ganze Welt von Programmiertechniken, die nicht diesem Dogma entsprechen.
Phoshi
32
Die Weitergabe von Funktionen an andere Funktionen ist ein so grundlegendes Konzept, dass Menschen, wenn eine Sprache dies nicht unterstützt, sich Mühe geben, triviale "Objekte" zu erstellen, deren einziger Zweck darin besteht, die fragliche Funktion als Notlösung einzuschließen.
Doval
36
Damals nannten wir diese Pass-Through-Funktionen "Rückrufe"
Mike

Antworten:

84

Das ist kein Problem.

Es ist eine bekannte Technik. Dies sind Funktionen höherer Ordnung (Funktionen, die Funktionen als Parameter annehmen).

Diese Art von Funktion ist auch ein grundlegender Baustein in der funktionalen Programmierung und wird häufig in funktionalen Sprachen wie Haskell verwendet .

Solche Funktionen sind nicht schlecht oder gut - wenn Sie noch nie auf den Begriff und die Technik gestoßen sind, können sie zunächst schwer zu erfassen sein, aber sie können sehr leistungsfähig sein und sind ein gutes Werkzeug für Ihren Werkzeuggürtel.

Oded
quelle
Vielen Dank, ich habe keine wirkliche Erfahrung mit funktionaler Programmierung, also werde ich mich darum kümmern. Es hat einfach nicht gut gepasst, weil meines Wissens nach, wenn Sie etwas als private Funktion deklarieren, nur diese Klasse Zugriff darauf haben sollte und öffentliche mit Bezug auf das Objekt aufgerufen werden sollten. Ich werde es mir genauer ansehen und ich gehe davon aus, dass ich es ein bisschen mehr zu schätzen weiß!
Elliot Blackburn
3
Lesen Sie weiter über Fortsetzungen , Fortsetzung vorbei Stil , Monaden (funktionale Programmierung) sollten ebenfalls nützlich sein.
Basile Starynkevitch
8
@BlueHat Sie etwas zu sein , privat deklarieren , wenn Sie die Kontrolle über wollen , wie es verwendet wird, wenn diese Benutzung als Rückruf für bestimmte Funktionen ist dann , die in Ordnung ist
Ratsche Freak
5
Genau. Wenn Sie es als privat kennzeichnen, möchten Sie nicht, dass andere Personen jederzeit mit Namen darauf zugreifen. Es ist absolut in Ordnung, eine private Funktion an eine andere Person weiterzugeben, weil Sie ausdrücklich möchten, dass diese sie so aufruft, wie sie es für diesen Parameter dokumentiert. Es muss nicht anders sein, als den Wert eines privaten Feldes als Parameter an eine andere Funktion zu übergeben, die vermutlich nicht schlecht zu Ihnen passt :-)
Steve Jessop
2
Es ist erwähnenswert, dass Sie es wahrscheinlich falsch machen , wenn Sie konkrete Klassen mit Funktionen als Konstruktorparameter schreiben .
Gusdor
30

Sie dienen nicht nur der funktionalen Programmierung. Sie können auch als Rückrufe bezeichnet werden :

Ein Rückruf ist ein ausführbarer Code, der als Argument an einen anderen Code übergeben wird, von dem erwartet wird, dass er das Argument zu einem geeigneten Zeitpunkt zurückruft (ausführt). Der Aufruf kann wie bei einem synchronen Rückruf sofort erfolgen oder zu einem späteren Zeitpunkt wie bei einem asynchronen Rückruf.

Denken Sie eine Sekunde lang an asynchronen Code. Sie übergeben eine Funktion, die beispielsweise Daten an den Benutzer sendet. Erst wenn der Code vollständig ist, rufen Sie diese Funktion mit dem Ergebnis der Antwort auf, mit der die Funktion die Daten an den Benutzer zurücksendet. Es ist eine Änderung der Denkweise.

Ich habe eine Bibliothek geschrieben, die Torrent-Daten aus Ihrer Seedbox abruft. Sie verwenden eine nicht blockierende Ereignisschleife, um diese Bibliothek auszuführen und Daten abzurufen. Anschließend geben Sie sie an den Benutzer zurück (z. B. in einem Websocket-Kontext). Stellen Sie sich vor, Sie haben 5 Personen in dieser Ereignisschleife verbunden und eine der Anfragen, um die Torrent-Daten von jemandem abzurufen. Das wird die ganze Schleife blockieren. Sie müssen also asynchron denken und Rückrufe verwenden - die Schleife läuft weiter und das "Zurückgeben der Daten an den Benutzer" wird nur ausgeführt, wenn die Ausführung der Funktion abgeschlossen ist, sodass Sie nicht darauf warten müssen. Feuer und vergessen.

James
quelle
1
+1 Für die Verwendung der Rückrufterminologie. Ich begegne diesem Begriff viel häufiger als "Funktionen höherer Ordnung".
Rodney Schuler
Diese Antwort gefällt mir, weil das OP Rückrufe zu beschreiben schien und nicht nur Funktionen höherer Ordnung im Allgemeinen: "Zum Beispiel übernimmt unsere Klasse, die den Aufruf an unsere Server vornimmt, eine Funktion, die dann ein Objekt aufruft und weiterleitet bis wann der Prozess abgeschlossen ist und Fehler behoben wurden etc. "
Ajedi32
11

Das ist keine schlechte Sache. In der Tat ist es eine sehr gute Sache.

Die Übergabe von Funktionen an Funktionen ist für die Programmierung so wichtig, dass wir Lambda-Funktionen als Kurzform erfunden haben . Zum Beispiel kann man Lambdas mit C ++ - Algorithmen verwenden , um sehr kompakten und dennoch aussagekräftigen Code zu schreiben, der es einem generischen Algorithmus ermöglicht, lokale Variablen und andere Zustände zu verwenden, um Dinge wie Suchen und Sortieren durchzuführen.

Objektorientierte Bibliotheken können auch Callbacks haben, die im Wesentlichen Schnittstellen sind, die eine kleine Anzahl von Funktionen spezifizieren (idealerweise eine, aber nicht immer). Man kann dann eine einfache Klasse erstellen, die diese Schnittstelle implementiert und ein Objekt dieser Klasse an eine Funktion weiterleitet. Dies ist ein Eckpfeiler der ereignisgesteuerten Programmierung , bei der Code auf Framework-Ebene (möglicherweise sogar in einem anderen Thread) ein Objekt aufrufen muss, um den Status als Reaktion auf eine Benutzeraktion zu ändern. Die ActionListener- Oberfläche von Java ist ein gutes Beispiel dafür.

Technisch gesehen ist ein C ++ - Funktor auch eine Art Rückrufobjekt, das syntaktischen Zucker nutzt operator()(), um dasselbe zu tun.

Schließlich gibt es Funktionszeiger im C-Stil, die nur in C verwendet werden sollten. Der Vollständigkeit halber möchte ich hier nicht auf Details eingehen. Die anderen oben erwähnten Abstraktionen sind weit überlegen und sollten in Sprachen verwendet werden, in denen sie vorkommen.

Andere haben die funktionale Programmierung erwähnt und wie selbstverständlich die Weitergabe von Funktionen in diesen Sprachen ist. Lambdas und Callbacks sind die Art und Weise, wie Verfahrens- und OOP-Sprachen dies imitieren, und sie sind sehr mächtig und nützlich.


quelle
Außerdem werden in C # sowohl Delegaten als auch Lambdas häufig verwendet.
Evil Dog Pie
6

Wie schon gesagt, ist es keine schlechte Übung. Es ist lediglich ein Weg, die Verantwortung zu entkoppeln und zu trennen. In OOP würden Sie beispielsweise Folgendes tun:

public void doSomethingGeneric(ISpecifier specifier) {
    //do generic stuff
    specifier.doSomethingSpecific();
    //do some other generic stuff
}

Die generische Methode delegiert eine bestimmte Aufgabe, von der sie nichts weiß, an ein anderes Objekt, das eine Schnittstelle implementiert. Die generische Methode kennt nur diese Schnittstelle. In Ihrem Fall wäre diese Schnittstelle eine aufzurufende Funktion.

Philipp Murry
quelle
1
Diese Redewendung hüllt die Funktion einfach in eine Schnittstelle und führt etwas aus, das der Übergabe einer
Ja, ich wollte nur zeigen, dass dies keine ungewöhnliche Praxis ist.
Philipp Murry
3

Im Allgemeinen ist es nicht falsch, Funktionen an andere Funktionen zu übergeben. Wenn Sie asynchrone Anrufe tätigen und mit dem Ergebnis etwas anfangen möchten, benötigen Sie eine Art Rückrufmechanismus.

Es gibt jedoch einige mögliche Nachteile einfacher Rückrufe:

  • Das Tätigen einer Reihe von Anrufen kann ein tiefes Verschachteln von Rückrufen erfordern.
  • Die Fehlerbehandlung kann eine Wiederholung für jeden Anruf in einer Abfolge von Aufrufen erfordern.
  • Es ist umständlich, mehrere Anrufe zu koordinieren, z. B. mehrere Anrufe gleichzeitig zu tätigen und dann etwas zu tun, wenn alle beendet sind.
  • Es gibt keine allgemeine Möglichkeit, eine Reihe von Anrufen abzubrechen.

Mit einfachen Webservices funktioniert das einwandfrei, aber es wird umständlich, wenn Sie eine komplexere Abfolge von Anrufen benötigen. Es gibt jedoch einige Alternativen. Bei JavaScript hat sich zum Beispiel die Verwendung von Versprechungen verschoben ( Was ist so toll an Javascript-Versprechungen? ).

Sie beinhalten immer noch die Übergabe von Funktionen an andere Funktionen, aber asynchrone Aufrufe geben einen Wert zurück, der einen Rückruf entgegennimmt, anstatt einen Rückruf direkt selbst entgegenzunehmen. Dies bietet mehr Flexibilität beim Zusammenstellen dieser Anrufe. So etwas kann ziemlich einfach in ActionScript implementiert werden.

fgb
quelle
Ich möchte auch hinzufügen, dass die unnötige Verwendung von Rückrufen es für jeden, der den Code liest, schwieriger machen kann, dem Ablauf der Ausführung zu folgen. Ein expliziter Aufruf ist einfacher zu verstehen als ein Rückruf, der in einem entfernten Teil des Codes festgelegt wurde. (Haltepunkte zur Rettung!) Auf diese Weise werden Ereignisse im Browser und in anderen ereignisbasierten Systemen ausgelöst. Dann müssen wir die Strategie des Event-Emitters verstehen, um zu wissen, wann und in welcher Reihenfolge Event-Handler aufgerufen werden.
Joeytwiddle