Przekazywanie funkcji na inne funkcje jako parametry, zła praktyka?

40

Jesteśmy w trakcie zmiany sposobu, w jaki nasza aplikacja AS3 komunikuje się z naszym zapleczem, i jesteśmy w trakcie wdrażania systemu REST, aby zastąpić nasz stary.

Niestety deweloper, który rozpoczął pracę, jest teraz na długoterminowym zwolnieniu chorobowym i został mi przekazany. Pracowałem z tym przez mniej więcej tydzień i rozumiem system, ale martwi mnie jedna rzecz. Wydaje się, że wiele funkcji przechodzi w funkcje. Na przykład nasza klasa, która wywołuje nasze serwery, przyjmuje funkcję, którą następnie wywoła i przekaże obiekt po zakończeniu procesu i obsłużeniu błędów itp.

Daje mi to „złe przeczucie”, kiedy czuję, że to okropna praktyka. Mogę wymyślić kilka powodów, ale chcę trochę potwierdzenia, zanim zaproponuję przerobienie systemu. Zastanawiałem się, czy ktoś miał jakieś doświadczenie z tym możliwym problemem?

Elliot Blackburn
źródło
22
Dlaczego tak się czujesz? Czy masz doświadczenie w programowaniu funkcjonalnym? (Zakładam, że nie, ale powinieneś
rzucić
8
Rozważ to jako lekcję! To, czego naucza środowisko akademickie i co jest naprawdę przydatne, często zaskakująco niewiele się pokrywa. Istnieje cały świat technik programowania, które nie są zgodne z tego rodzaju dogmatami.
Phoshi,
32
Przekazywanie funkcji innym funkcjom jest tak fundamentalną koncepcją, że gdy język go nie obsługuje, ludzie robią wszystko, co w ich mocy, aby tworzyć trywialne „obiekty”, których jedynym celem jest zatrzymanie danej funkcji jako stopgap.
Doval,
36
Wcześniej nazywaliśmy te funkcje przekazywania „Oddzwanianiem”
Mike

Odpowiedzi:

84

To nie jest problem.

To znana technika. Są to funkcje wyższego rzędu (funkcje, które przyjmują funkcje jako parametry).

Ten rodzaj funkcji jest również podstawowym elementem składowym programowania funkcjonalnego i jest szeroko stosowany w językach funkcjonalnych, takich jak Haskell .

Takie funkcje nie są złe ani dobre - jeśli nigdy nie spotkałeś się z pojęciem i techniką, na początku mogą być trudne do uchwycenia, ale mogą być bardzo potężne i są dobrym narzędziem w pasku narzędzi.

Oded
źródło
Dzięki za to, nie mam prawdziwego doświadczenia z programowaniem funkcjonalnym, więc przyjrzę się temu. Po prostu nie działało dobrze, ponieważ z mojej małej wiedzy, kiedy deklarujesz coś jako funkcję prywatną, wtedy tylko ta klasa powinna mieć do niej dostęp, a klasy publiczne powinny być wywoływane w odniesieniu do obiektu. Przyjrzę się temu właściwie i spodziewam się, że docenię to jeszcze bardziej!
Elliot Blackburn,
3
Przydatne powinny być również czytanie o kontynuacjach , stylu przekazywania kontynuacji , monadach (programowanie funkcjonalne) .
Basile Starynkevitch,
8
@BlueHat deklarujesz coś jako prywatny, jeśli chcesz kontrolować sposób, w jaki jest używany, jeśli to użycie jest oddzwanianiem do określonych funkcji, to jest w porządku
maniak ratchet
5
Dokładnie. Oznaczenie go jako prywatnego oznacza, że ​​nie chcesz, aby ktoś inny wchodził i uzyskiwał do niego dostęp po imieniu, kiedy tylko zechce. Przekazywanie funkcji prywatnej komuś innemu, ponieważ chcesz , aby wywoływała ją w sposób, w jaki dokumentują ten parametr, jest absolutnie w porządku. Nie musi być niczym innym, jak przekazaniem wartości pola prywatnego jako parametru do innej funkcji, która prawdopodobnie nie pasuje do ciebie :-)
Steve Jessop
2
Warto zauważyć, że jeśli piszesz konkretne klasy z funkcjami jako parametrami konstruktora, prawdopodobnie robisz to źle .
Gusdor,
30

Nie służą tylko do programowania funkcjonalnego. Mogą być również nazywane wywołaniami zwrotnymi :

Wywołanie zwrotne to fragment kodu wykonywalnego, który jest przekazywany jako argument do innego kodu, który powinien wywołać (wykonać) argument w dogodnym czasie. Wywołanie może być natychmiastowe, jak w synchronicznym wywołaniu zwrotnym, lub może się zdarzyć w późniejszym czasie, jak w przypadku asynchronicznego wywołania zwrotnego.

Pomyśl przez chwilę o kodzie asynchronicznym. Przekazujesz funkcję, która na przykład wysyła dane do użytkownika. Dopiero po zakończeniu kodu wywołujesz tę funkcję z wynikiem odpowiedzi, której następnie używa do wysłania danych z powrotem do użytkownika. To zmiana sposobu myślenia.

Napisałem bibliotekę, która pobiera dane Torrenta z twojego seedboxa. Używasz nieblokującej się pętli zdarzeń, aby uruchomić tę bibliotekę i uzyskać dane, a następnie zwrócić je użytkownikowi (powiedzmy w kontekście websocket). Wyobraź sobie, że masz 5 osób połączonych w tej pętli zdarzeń i jedna z próśb o czyjąś utratę danych. To zablokuje całą pętlę. Musisz więc myśleć asynchronicznie i używać wywołań zwrotnych - pętla nadal działa, a „oddawanie danych użytkownikowi” działa tylko wtedy, gdy funkcja zakończy wykonywanie, więc nie trzeba na nią czekać. Ogień i zapomnij.

James
źródło
1
+1 Za użycie terminologii zwrotnej. Termin ten spotykam znacznie częściej niż „funkcje wyższego rzędu”.
Rodney Schuler,
Podoba mi się ta odpowiedź, ponieważ OP wydaje się opisywać w szczególności funkcje zwrotne, a nie ogólnie funkcje wyższego rzędu: „Na przykład nasza klasa, która wywołuje nasze serwery, przyjmuje funkcję, którą następnie wywołuje i przekazuje obiekt do momentu zakończenia procesu i usunięcia błędów itp. ”
Ajedi32,
11

to nie jest zła rzecz. W rzeczywistości jest to bardzo dobra rzecz.

Przekazywanie funkcji do funkcji jest tak ważne w programowaniu, że wymyśliliśmy funkcje lambda jako skrót. Na przykład, można użyć lambdas z algorytmami C ++ do napisania bardzo zwartego, ale ekspresyjnego kodu, który pozwala ogólnemu algorytmowi na użycie zmiennych lokalnych i innych stanów do takich rzeczy jak wyszukiwanie i sortowanie.

Biblioteki obiektowe mogą również zawierać wywołania zwrotne, które są zasadniczo interfejsami określającymi niewielką liczbę funkcji (najlepiej jedną, ale nie zawsze). Następnie można utworzyć prostą klasę, która implementuje ten interfejs i przekazać obiekt tej klasy do funkcji. Jest to podstawa programowania sterowanego zdarzeniami , w którym kod na poziomie frameworku (być może nawet w innym wątku) musi wywoływać obiekt, aby zmienić stan w odpowiedzi na akcję użytkownika. Interfejs ActionListener w Javie jest tego dobrym przykładem.

Z technicznego punktu widzenia funktor C ++ jest także rodzajem obiektu zwrotnego, który wykorzystuje cukier składniowy operator()(), aby zrobić to samo.

Wreszcie, istnieją wskaźniki funkcji w stylu C, które powinny być używane tylko w C. Nie będę wchodził w szczegóły, po prostu wymieniam je dla kompletności. Pozostałe abstrakcje wymienione powyżej są znacznie lepsze i powinny być używane w językach, które je mają.

Inni wspominali o programowaniu funkcjonalnym i tym, jak przekazywanie funkcji jest bardzo naturalne w tych językach. Lambdas i callbacki to sposób, w jaki naśladują je języki proceduralne i OOP, i są bardzo wydajne i użyteczne.


źródło
Ponadto w C # delegaci i lambdy są szeroko stosowane.
Evil Dog Pie
6

Jak już powiedziano, nie jest to zła praktyka. To tylko sposób na oddzielenie i oddzielenie odpowiedzialności. Na przykład w OOP zrobiłbyś coś takiego:

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

Metoda ogólna polega na delegowaniu określonego zadania - o którym nic nie wie - na inny obiekt, który implementuje interfejs. Metoda ogólna zna tylko ten interfejs. W twoim przypadku ten interfejs byłby funkcją do wywołania.

Philipp Murry
źródło
1
Ten idiom po prostu pakuje funkcję w interfejs, osiągając coś bardzo podobnego do przekazania surowej funkcji.
Tak, chciałem tylko pokazać, że nie jest to rzadka praktyka.
Philipp Murry
3

Ogólnie rzecz biorąc, nie ma nic złego w przekazywaniu funkcji do innych funkcji. Jeśli wykonujesz połączenia asynchroniczne i chcesz zrobić coś z wynikiem, potrzebujesz jakiegoś mechanizmu oddzwaniania.

Istnieje jednak kilka potencjalnych wad prostych wywołań zwrotnych:

  • Wykonywanie serii połączeń może wymagać głębokiego zagnieżdżenia połączeń zwrotnych.
  • Obsługa błędów może wymagać powtórzenia dla każdego połączenia w sekwencji połączeń.
  • Koordynacja wielu połączeń jest niewygodna, np. Wykonywanie wielu połączeń jednocześnie, a następnie robienie czegoś po ich zakończeniu.
  • Nie ma ogólnego sposobu anulowania zestawu połączeń.

Dzięki prostym usługom internetowym sposób, w jaki to robisz, działa dobrze, ale staje się niewygodny, jeśli potrzebujesz bardziej złożonego sekwencjonowania połączeń. Istnieje jednak kilka alternatyw. Na przykład w JavaScript nastąpiło przesunięcie w kierunku korzystania z obietnic ( co jest takiego wspaniałego w obietnicach javascript ).

Wciąż wiążą się z przekazywaniem funkcji do innych funkcji, ale wywołania asynchroniczne zwracają wartość, która odbiera wywołanie zwrotne zamiast odbierać je bezpośrednio. Daje to większą elastyczność w łączeniu tych połączeń. Coś takiego można dość łatwo zaimplementować w ActionScript.

FGB
źródło
Dodałbym również, że niepotrzebne użycie wywołań zwrotnych może utrudnić śledzenie przepływu wykonania dla każdego, kto czyta kod. Wyraźne wywołanie jest łatwiejsze do zrozumienia niż wywołanie zwrotne ustawione w odległej części kodu. (Punkty przerwania na ratunek!) Niemniej jednak tak właśnie uruchamiają się zdarzenia w przeglądarce i innych systemach opartych na zdarzeniach; następnie musimy zrozumieć strategię emitera zdarzeń, aby wiedzieć, kiedy i w jakiej kolejności będą wywoływane moduły obsługi zdarzeń.
joeytwiddle