Czy w tej funkcji należy umieścić funkcje, które są używane tylko w jednej innej funkcji?
30
W szczególności piszę w JavaScript.
Powiedzmy, że moją podstawową funkcją jest funkcja A. Jeśli funkcja A wykonuje kilka wywołań do funkcji B, ale funkcja B nie jest używana nigdzie indziej, to czy powinienem po prostu umieścić funkcję B w funkcji A?
Czy to dobra praktyka? Czy powinienem nadal ustawić Funkcję B w tym samym zakresie, co Funkcję A?
Ogólnie jestem zwolennikiem funkcji zagnieżdżonych, szczególnie w JavaScript.
W JavaScript jedynym sposobem ograniczenia widoczności funkcji jest zagnieżdżenie jej w innej funkcji.
Funkcja pomocnika jest prywatnym szczegółem implementacji. Umieszczenie go w tym samym zakresie jest podobne do upublicznienia prywatnych funkcji klasy.
Jeśli okaże się, że jest bardziej ogólny, łatwo się wyprowadzić, ponieważ możesz mieć pewność, że pomocnik jest obecnie używany tylko przez tę jedną funkcję. Innymi słowy, sprawia, że kod jest bardziej spójny.
Często można wyeliminować parametry, dzięki czemu podpis i implementacja funkcji są mniej szczegółowe. To nie to samo, co uczynienie zmiennych globalnymi. To bardziej jak użycie członka klasy w metodzie prywatnej.
Możesz nadać funkcjom pomocnika lepsze, prostsze nazwy, nie martwiąc się o konflikty.
Funkcja pomocnika jest łatwiejsza do znalezienia. Tak, są narzędzia, które mogą ci pomóc. Wciąż łatwiej jest po prostu trochę przesunąć oczy w górę ekranu.
Kod naturalnie tworzy rodzaj drzewa abstrakcji: kilka ogólnych funkcji u podstawy, rozgałęzionych na kilka szczegółów implementacji. Jeśli używasz edytora ze składaniem / zwijaniem funkcji, zagnieżdżanie tworzy hierarchię ściśle powiązanych funkcji na tym samym poziomie abstrakcji. Dzięki temu bardzo łatwo jest przestudiować kod na wymaganym poziomie i ukryć szczegóły.
Myślę, że wiele sprzeciwów wynika z faktu, że większość programistów była wychowana w tradycji C / C ++ / Java lub była nauczana przez kogoś innego. Funkcje zagnieżdżone nie wyglądają naturalnie, ponieważ nie byliśmy zbytnio na nie narażeni podczas nauki programowania. To nie znaczy, że nie są przydatne.
Powinieneś umieścić go w zasięgu globalnym, z kilku powodów.
Zagnieżdżenie funkcji pomocnika w dzwoniącym zwiększa długość dzwoniącego. Długość funkcji jest prawie zawsze wskaźnikiem ujemnym; krótkie funkcje są łatwiejsze do zrozumienia, zapamiętania, debugowania i utrzymania.
Jeśli funkcja pomocnika ma sensowną nazwę, wystarczy odczytać tę nazwę bez konieczności zobaczenia definicji w pobliżu. Jeśli zrobić potrzebę patrz definicja pomocnika, aby zrozumieć funkcję rozmówcy, a następnie, że rozmówca robi zbyt dużo, albo pracuje na zbyt wielu poziomach abstrakcji równocześnie.
Posiadanie pomocnika na całym świecie pozwala wywoływać go innym funkcjom, jeśli w końcu okaże się ogólnie przydatne. Jeśli pomocnik nie jest dostępny, masz ochotę go wyciąć i wkleić, zapomnieć i ponownie go zaimplementować, źle, lub sprawić, by inna funkcja była dłuższa niż to konieczne.
Zagnieżdżenie funkcji pomocnika zwiększa pokusę używania zmiennych z zakresu dzwoniącego bez deklaracji, przez co staje się niejasne, jakie są wejścia i wyjścia pomocnika. Jeśli funkcja nie określa jasno, na jakich danych operuje i jakie ma efekty, zwykle oznacza to niejasną odpowiedzialność. Zadeklarowanie osobnika pomocnika jako samodzielnej funkcji zmusza do znajomości tego, co faktycznie robi.
Edytować
To okazało się bardziej kontrowersyjne pytanie, niż myślałem. Wyjaśnić:
W JavaScript duże funkcje rozpinania plików często pełnią rolę klas, ponieważ język nie zapewnia żadnego innego mechanizmu ograniczającego zakres. Z pewnością funkcje pomocnicze powinny wchodzić w takie quasi-klasy, a nie poza nimi.
A punkt o łatwiejszych ponownego wykorzystania zakłada, że jeśli podprogram ma stać się bardziej powszechnie używane, są chętni, aby przenieść go na miejscu w ogóle i umieścić go w odpowiednim miejscu, np biblioteki użytkowego lub łańcuch do swojego światowego rejestru konfiguracyjnego. Jeśli nie chcesz zamówić swój kod tak, to równie dobrze można zagnieździć podprogram, tak jak byś zrobił z normalnym Method Object w bardziej „blokowe” języka.
Dzięki, wszystkie bardzo dobre punkty. Na marginesie, w jaki sposób ogólnie powinienem zamówić moje funkcje? Na przykład w tej chwili, przy małych programach, zamawiam je alfabetycznie. Ale czy powinienem grupować funkcje pomocnika i dzwoniącego? Chyba powinienem przynajmniej rozbić swoje funkcje na podstawie, na przykład, do których części aplikacji się odnoszą?
Gary,
Dawno nie zawracałem sobie głowy definicjami metod. Jeśli programujesz z jakimkolwiek stopniem profesjonalizmu, powinieneś mieć środowisko, które pozwala przeskoczyć do dowolnej definicji za pomocą kilku naciśnięć klawiszy. Dlatego krótkie metody są przydatne, ale krótkie lub nawet dobrze uporządkowane pliki klas mają niewielką wartość. (Oczywiście JavaScript wymagał, aby wszystkie definicje były umieszczane ponad ich zastosowaniami, w przeciwnym razie zachowanie byłoby technicznie niezdefiniowane - nie można sobie przypomnieć, czy nadal tak jest, czy nie.)
Kilian Foth
2
To świetna odpowiedź. Problem zepsutej enkapsulacji jest czymś, czego wielu nie bierze pod uwagę przy użyciu funkcji wewnętrznych.
sbichenko,
2
Globale to zapach kodu. Powodują niepotrzebne sprzężenie, które zapobiega refaktoryzacji, powodują konflikty nazw, ujawniają szczegóły implementacji itp. Jest to szczególnie złe w JavaScript, ponieważ wszystkie zmienne są zmienne, kod jest zwykle ładowany bezpośrednio od stron trzecich, nie ma (jeszcze) powszechnie dostępny system modułowy, a nawet powszechnie dostępna implementacja „let”. W tak nieprzyjaznym środowisku jedyną strategią obronną, którą mamy, jest hermetyzacja w ramach funkcji jednego strzału.
Warbo,
1
„Wypełniając rolę klas” próbuje napisać JS, jakby to było coś innego. Jaka jest „rola klas”? Sprawdzanie typu? Modułowość? Kompilacja etapowa? Dziedzictwo? Pytanie dotyczy określania zakresu, więc zakładam, że masz na myśli prymitywne zasady określania zakresu klas. To tylko zaleta: w jaki sposób należy mierzyć zakres zajęć ? PHP czyni je globalnymi, co nie zapewnia enkapsulacji. Python analizuje klasy leksykalnie, ale jeśli jesteś w bloku o leksykalnym zasięgu, po co zawijać wszystko w innym bloku o zasięgu klasowym? JS nie ma takiej redundancji: wystarczy użyć tyle zakresu leksykalnego, ile potrzebujesz.
Warbo,
8
Zamierzam zaproponować trzecią ścieżkę, aby umieścić obie funkcje w zamknięciu. Wyglądałoby to tak:
var functionA =(function(){function functionB(){// do stuff...}function functionA(){// do stuff...
functionB();// do stuff...}return functionA;})();
Tworzymy zamknięcie owijając deklarację obu funkcji w Iife . Zwracana wartość IIFE jest funkcją publiczną, przechowywaną w zmiennej nazwy funkcji. Funkcję publiczną można wywołać dokładnie w taki sam sposób, jakby była zadeklarowana jako funkcja globalna, tj functionA(). Zwróć uwagę, że zwracana wartość jest funkcją , a nie wywołaniem funkcji, a zatem nie ma na końcu znaków parens.
Zawinięcie w ten sposób dwóch funkcji functionBjest teraz całkowicie prywatne i nie można uzyskać do niego dostępu poza zamknięciem, ale jest widoczne tylko dla functionA. Nie zaśmieca globalnej przestrzeni nazw i nie zaśmieca definicji functionA.
Zdefiniowanie funkcji B w funkcji A daje funkcji B dostęp do zmiennych wewnętrznych A.
Tak więc funkcja B zdefiniowana w funkcji A sprawia wrażenie (lub przynajmniej możliwości), że B używa lub zmienia lokalne zmienne A. Definicja B poza A wyjaśnia, że B nie.
Dlatego dla jasności kodu zdefiniowałbym B w obrębie A tylko wtedy, gdy B potrzebuje dostępu do zmiennych lokalnych A. Jeśli B ma wyraźny cel niezależny od A, zdecydowanie zdefiniowałbym go poza A.
Ponieważ funkcja B nie jest używana nigdzie indziej, równie dobrze możesz po prostu uczynić ją funkcją wewnętrzną w funkcji A, ponieważ jest to jedyny powód, dla którego istnieje.
Zamierzam zaproponować trzecią ścieżkę, aby umieścić obie funkcje w zamknięciu. Wyglądałoby to tak:
Tworzymy zamknięcie owijając deklarację obu funkcji w Iife . Zwracana wartość IIFE jest funkcją publiczną, przechowywaną w zmiennej nazwy funkcji. Funkcję publiczną można wywołać dokładnie w taki sam sposób, jakby była zadeklarowana jako funkcja globalna, tj
functionA()
. Zwróć uwagę, że zwracana wartość jest funkcją , a nie wywołaniem funkcji, a zatem nie ma na końcu znaków parens.Zawinięcie w ten sposób dwóch funkcji
functionB
jest teraz całkowicie prywatne i nie można uzyskać do niego dostępu poza zamknięciem, ale jest widoczne tylko dlafunctionA
. Nie zaśmieca globalnej przestrzeni nazw i nie zaśmieca definicjifunctionA
.źródło
Zdefiniowanie funkcji B w funkcji A daje funkcji B dostęp do zmiennych wewnętrznych A.
Tak więc funkcja B zdefiniowana w funkcji A sprawia wrażenie (lub przynajmniej możliwości), że B używa lub zmienia lokalne zmienne A. Definicja B poza A wyjaśnia, że B nie.
Dlatego dla jasności kodu zdefiniowałbym B w obrębie A tylko wtedy, gdy B potrzebuje dostępu do zmiennych lokalnych A. Jeśli B ma wyraźny cel niezależny od A, zdecydowanie zdefiniowałbym go poza A.
źródło
Nie należy go zagnieżdżać, ponieważ w przeciwnym razie funkcja zagnieżdżona zostanie odtworzona za każdym razem, gdy wywołasz funkcję zewnętrzną.
źródło
Ponieważ funkcja B nie jest używana nigdzie indziej, równie dobrze możesz po prostu uczynić ją funkcją wewnętrzną w funkcji A, ponieważ jest to jedyny powód, dla którego istnieje.
źródło