Zauważyłem, że kilka funkcji, z którymi pracuję, ma 6 lub więcej parametrów, podczas gdy w większości bibliotek, których używam, rzadko jest znalezienie funkcji, która zajmuje więcej niż 3.
Często wiele z tych dodatkowych parametrów to opcje binarne zmieniające zachowanie funkcji. Myślę, że niektóre z tych funkcji o sparametryzowanych parametrach powinny być prawdopodobnie refaktoryzowane. Czy istnieje wskazówka, która liczba jest zbyt duża?
functions
parameters
Darth Egregious
źródło
źródło
Odpowiedzi:
Nigdy nie widziałem wytycznych, ale z mojego doświadczenia wynika, że funkcja, która przyjmuje więcej niż trzy lub cztery parametry, wskazuje na jeden z dwóch problemów:
Trudno powiedzieć, na co patrzysz, bez dodatkowych informacji. Możliwe, że refaktoryzacja, którą musisz zrobić, polega na podzieleniu funkcji na mniejsze funkcje, które są wywoływane od rodzica, w zależności od flag, które są obecnie przekazywane do funkcji.
Robiąc to, można osiągnąć kilka dobrych korzyści:
if
struktury, która wywołuje wiele metod o opisowych nazwach, niż ze struktury, która robi to wszystko w jednej metodzie.źródło
Zgodnie z „Clean Code: A Handbook of Agile Software Craftsmanship” zero to ideał, jeden lub dwa są dopuszczalne, trzy w szczególnych przypadkach i cztery lub więcej, nigdy!
Słowa autora:
W tej książce jest rozdział mówiący tylko o funkcjach, w których omawiane są duże parametry, więc myślę, że ta książka może być dobrą wskazówką, ile parametrów potrzebujesz.
Moim osobistym zdaniem jeden parametr jest lepszy niż nikt, ponieważ myślę, że jest bardziej jasne, co się dzieje.
Jako przykład, moim zdaniem, drugi wybór jest lepszy, ponieważ jest bardziej jasne, co metoda przetwarza:
vs.
W przypadku wielu parametrów może to oznaczać, że niektóre zmienne można pogrupować w jeden obiekt lub, w tym przypadku, wiele boolanów może oznaczać, że funkcja / metoda robi więcej niż jedną rzecz, w tym przypadku lepiej jest refaktoryzować każde z tych zachowań w innej funkcji.
źródło
String language = detectLanguage(someText);
. W obu przypadkach przekazałeś dokładnie taką samą liczbę argumentów, po prostu zdarza się, że podzieliłeś wykonanie funkcji na dwie części z powodu złego języka.Matrix.Create(input);
gdzieinput
, powiedzmy, .NETIEnumerable<SomeAppropriateType>
? W ten sposób nie potrzebujesz również osobnego przeciążenia, jeśli chcesz utworzyć matrycę zawierającą 10 elementów zamiast 9.Jeśli klasy domen w aplikacji są poprawnie zaprojektowane, liczba parametrów, które przekazujemy do funkcji, zostanie automatycznie zmniejszona - ponieważ klasy wiedzą, jak wykonywać swoją pracę i mają wystarczającą ilość danych, aby wykonać swoją pracę.
Załóżmy na przykład, że masz klasę menedżera, która prosi klasę 3 klasy o wykonanie zadań.
Jeśli modelujesz poprawnie,
To jest proste.
Jeśli nie masz poprawnego modelu, metoda będzie następująca
Właściwy model zawsze redukuje parametry funkcji między wywołaniami metod, ponieważ prawidłowe funkcje są delegowane do ich własnych klas (pojedyncza odpowiedzialność) i mają wystarczającą ilość danych do wykonania swojej pracy.
Ilekroć widzę wzrost liczby parametrów, sprawdzam mój model, aby sprawdzić, czy poprawnie zaprojektowałem swój model aplikacji.
Istnieją jednak pewne wyjątki: gdy muszę utworzyć obiekt przesyłania lub obiekty konfiguracji, użyję wzorca konstruktora do wytworzenia małych obiektów zbudowanych przed zbudowaniem dużego obiektu konfiguracji.
źródło
Jednym z aspektów, którego inne odpowiedzi nie podejmują, jest wydajność.
Jeśli programujesz w wystarczająco niskim języku (C, C ++, asembler), duża liczba parametrów może być bardzo niekorzystna dla wydajności na niektórych architekturach, zwłaszcza jeśli funkcja jest wywoływana wiele razy.
Kiedy wywołanie funkcji jest wykonany w ramię na przykład, pierwsze cztery argumenty są umieszczone w rejestrach
r0
dor3
i pozostałe argumenty muszą być odłożony na stosie. Utrzymywanie liczby argumentów poniżej pięciu może mieć duże znaczenie dla funkcji krytycznych.Do funkcji, które nazywane są bardzo często, nawet fakt, że program skonfigurować argumenty przed każde wywołanie może mieć wpływ na wydajność (
r0
dor3
może być nadpisane przez wywołanej funkcji i będą musiały być zastąpione przed następnym wywołaniu), więc w tym względzie zero argumentów jest najlepsze.Aktualizacja:
KjMag porusza ciekawy temat inliningu. Inlining pod pewnymi względami złagodzi to, ponieważ pozwoli kompilatorowi wykonać te same optymalizacje, które byłyby możliwe w przypadku pisania w czystym asemblerze. Innymi słowy, kompilator może zobaczyć, które parametry i zmienne są używane przez wywoływaną funkcję, i może zoptymalizować użycie rejestru, aby zminimalizować odczyt / zapis stosu.
Istnieją jednak pewne problemy z wprowadzaniem.
inline
we wszystkich przypadkach był traktowany jako obowiązkowy.inline
lub faktycznie dadzą ci błędy, gdy je napotkają.źródło
inline
.)Gdy lista parametrów wzrośnie do więcej niż pięciu, zastanów się nad zdefiniowaniem struktury lub obiektu „kontekstowego”.
Jest to po prostu struktura, która przechowuje wszystkie opcjonalne parametry z pewnymi rozsądnymi ustawieniami domyślnymi.
W świecie proceduralnym C wystarczyłaby prosta struktura. W Javie, C ++ wystarczy prosty obiekt. Nie zadzieraj z modułami pobierającymi lub ustawiającymi, ponieważ jedynym celem obiektu jest przechowywanie „publicznie” ustawialnych wartości.
źródło
Nie, nie ma standardowych wytycznych
Ale istnieją pewne techniki, które mogą sprawić, że funkcja o wielu parametrach będzie bardziej znośna.
Możesz użyć parametru list-if-args (args *) lub parametru słownik-of-args (kwargs
**
)Na przykład w python:
Wyjścia:
Lub możesz użyć składni definicji literału obiektu
Na przykład oto wywołanie JavaScript jQuery w celu uruchomienia żądania AJAX GET:
Jeśli spojrzysz na klasę ajax w jQuery, istnieje wiele (około 30) dodatkowych właściwości, które można ustawić; głównie dlatego, że komunikacja ajaxowa jest bardzo złożona. Na szczęście dosłowna składnia obiektu ułatwia życie.
Intellisense C # zapewnia aktywną dokumentację parametrów, więc często zdarza się, że widzimy bardzo złożone układy przeciążonych metod.
Dynamicznie pisane języki, takie jak python / javascript, nie mają takiej możliwości, więc o wiele bardziej powszechne są argumenty słów kluczowych i definicje literałów obiektowych.
Wolę definicje literału obiektu ( nawet w języku C # ) do zarządzania złożonymi metodami, ponieważ możesz wyraźnie zobaczyć, które właściwości są ustawiane, gdy obiekt jest tworzony. Będziesz musiał wykonać trochę więcej pracy, aby obsłużyć domyślne argumenty, ale na dłuższą metę twój kod będzie o wiele bardziej czytelny. Dzięki dosłownym definicjom obiektów możesz zerwać z zależnością od dokumentacji, aby zrozumieć, co robi Twój kod na pierwszy rzut oka.
IMHO, przeciążone metody są mocno przereklamowane.
Uwaga: Jeśli dobrze pamiętam, kontrola dostępu tylko do odczytu powinna działać dla konstruktorów dosłownych obiektów w języku C #. Działają one zasadniczo tak samo, jak ustawianie właściwości w konstruktorze.
Jeśli nigdy nie pisałeś żadnego trywialnego kodu w języku dynamicznie typowanym (python) i / lub funkcjonalnym / prototypowym języku opartym na javaScript, zdecydowanie polecam wypróbowanie go. To może być oświecające doświadczenie.
Najpierw przerażające może być przerwanie polegania na parametrach w podejściu do inicjalizacji funkcji / metody na końcu, ale nauczysz się robić o wiele więcej za pomocą swojego kodu bez konieczności dodawania niepotrzebnej złożoności.
Aktualizacja:
Prawdopodobnie powinienem był podać przykłady demonstrujące użycie w języku o typie statycznym, ale obecnie nie myślę w kontekście o typie statycznym. Zasadniczo wykonałem zbyt wiele pracy w dynamicznie wpisywanym kontekście, aby nagle wrócić.
Wiem , że składnia definicji literału obiektu jest całkowicie możliwa w językach o typie statycznym (przynajmniej w języku C # i Javie), ponieważ używałem ich wcześniej. W językach o typie statycznym nazywane są „inicjalizatorami obiektów”. Oto kilka linków pokazujących ich użycie w Javie i C # .
źródło
Osobiście więcej niż 2 powoduje uruchomienie alarmu zapachu mojego kodu. Kiedy uznajesz funkcje za operacje (to znaczy tłumaczenie z wejścia na wyjście), nierzadko zdarza się, że w operacji używane są więcej niż 2 parametry. Procedury (czyli szereg kroków prowadzących do osiągnięcia celu) wymagają większego wkładu i czasami są najlepszym podejściem, ale w większości języków te dni nie powinny być normą.
Ale znowu jest to raczej wytyczna niż reguła. Często mam funkcje, które wymagają więcej niż dwóch parametrów z powodu nietypowych okoliczności lub łatwości użytkowania.
źródło
Bardzo, jak mówi Evan Plaice, jestem wielkim fanem po prostu przekazywania tablic asocjacyjnych (lub porównywalnej struktury danych twojego języka) do funkcji, gdy tylko jest to możliwe.
Dlatego zamiast (na przykład) to:
Idź po:
Wordpress robi wiele rzeczy w ten sposób i myślę, że działa dobrze. (Chociaż mój powyższy przykładowy kod jest wymyślony i sam nie jest przykładem z Wordpress.)
Ta technika umożliwia łatwe przesyłanie dużej ilości danych do funkcji, ale uwalnia od konieczności pamiętania kolejności, w jakiej każda z nich musi zostać przekazana.
Docenisz również tę technikę, gdy przyjdzie czas na refaktoryzację - zamiast potencjalnie zmieniać kolejność argumentów funkcji (na przykład, gdy zdasz sobie sprawę, że musisz przekazać jeszcze inny argument), nie musisz zmieniać parametru funkcji lista w ogóle.
Nie tylko oszczędza to konieczności ponownego zapisywania definicji funkcji - oszczędza także konieczności zmiany kolejności argumentów przy każdym wywołaniu funkcji. To ogromna wygrana.
źródło
Poprzednia odpowiedź wspominała wiarygodnego autora, który stwierdził, że im mniej parametrów mają twoje funkcje, tym lepiej sobie radzisz. Odpowiedź nie wyjaśniła, dlaczego, ale książki to wyjaśniają, a oto dwa najbardziej przekonujące powody, dla których musisz przyjąć tę filozofię, z którymi osobiście się zgadzam:
Parametry należą do poziomu abstrakcji, który różni się od poziomu funkcji. Oznacza to, że czytelnik twojego kodu będzie musiał pomyśleć o naturze i celu parametrów twoich funkcji: takie myślenie jest „niższym poziomem” niż nazwa i cel odpowiednich funkcji.
Drugim powodem, dla którego funkcja ma jak najmniej parametrów, jest testowanie: na przykład, jeśli masz funkcję z 10 parametrami, zastanów się, ile kombinacji parametrów musisz pokryć wszystkie przypadki testowe, na przykład dla jednostki test. Mniej parametrów = mniej testów.
źródło
Aby zapewnić nieco więcej kontekstu wokół porady, że idealna liczba argumentów funkcji jest zerowa w „Czystym kodzie: Podręcznik do spraw zwinnego oprogramowania” Roberta Martina, autor mówi jako jeden ze swoich punktów:
Na
includeSetupPage()
powyższym przykładzie poniżej znajduje się niewielki fragment jego refaktoryzowanego „czystego kodu” na końcu rozdziału:Autorska „szkoła myślenia” przemawia za małymi klasami, niską (najlepiej 0) liczbą argumentów funkcji i bardzo małymi funkcjami. Chociaż również nie do końca się z nim zgadzam, przekonałem się, że warto zastanowić się nad ideą argumentów z zerową funkcją jako ideałem. Zauważ też, że nawet jego mały fragment kodu powyżej ma również niezerowe funkcje argumentów, więc myślę, że zależy to od kontekstu.
(Jak zauważyli inni, twierdzi on również, że więcej argumentów utrudnia testowanie z punktu widzenia testowania. Ale tutaj chciałem przede wszystkim podkreślić powyższy przykład i jego uzasadnienie dla argumentów o zerowej funkcji).
źródło
Idealnie zero. Jeden lub dwa są w porządku, trzy w niektórych przypadkach.
Cztery lub więcej to zwykle zła praktyka.
Oprócz zasad pojedynczej odpowiedzialności, które zauważyli inni, możesz również pomyśleć o tym z perspektywy testowania i debugowania.
Jeśli jest jeden parametr, znajomość jego wartości, testowanie ich i znajdowanie z nimi błędu jest „stosunkowo łatwe, ponieważ istnieje tylko jeden czynnik. Wraz ze wzrostem czynników całkowita złożoność szybko rośnie. Na przykład abstrakcyjny:
Zastanów się nad programem „w co się ubrać w taką pogodę”. Zastanów się, co może zrobić z jednym wejściem - temperaturą. Jak możesz sobie wyobrazić, wyniki ubrań są dość proste w oparciu o ten jeden czynnik. Teraz zastanów się, co program może / powinien / powinien zrobić, jeśli faktycznie przekroczy temperaturę, wilgotność, punkt rosy, opady itp. Teraz wyobraź sobie, jak trudno byłoby debugować, gdyby dał „niewłaściwą” odpowiedź na coś.
źródło