Czy istnieją wytyczne dotyczące liczby parametrów, które funkcja powinna zaakceptować?

114

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?

Darth Egregious
źródło
4
@Ominus: Chodzi o to, że chcesz skoncentrować swoje zajęcia. Skoncentrowane klasy zwykle nie mają tak wielu zależności / atrybutów, dlatego masz mniej parametrów do konstruktora. Niektóre bzyczące słowa, które ludzie w tym momencie rzucają, to zasada wysokiej spójności i pojedynczej odpowiedzialności . Jeśli uważasz, że nie zostały naruszone i nadal potrzebujesz wielu parametrów, rozważ użycie wzorca Konstruktora.
c_maker
2
Zdecydowanie nie podążaj za przykładem MPI_Sendrecv () , który przyjmuje 12 parametrów!
chrisaycock
6
Projekt, nad którym obecnie pracuję, wykorzystuje pewien framework, w którym metody o ponad 10 parametrach są powszechne. W kilku miejscach nazywam jedną konkretną metodę z 27 parametrami. Za każdym razem, gdy to widzę, umieram trochę w środku.
perp
3
Nigdy nie dodawaj przełączników boolowskich, aby zmienić zachowanie funkcji. Zamiast tego podziel funkcję. Przełóż wspólne zachowanie na nowe funkcje.
kevin cline
2
@Ominus What? Tylko 10 parametrów? To nic, potrzeba znacznie więcej . : D
maaartinus

Odpowiedzi:

106

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:

  1. Ta funkcja robi za dużo. Powinien zostać podzielony na kilka mniejszych funkcji, z których każda ma mniejszy zestaw parametrów.
  2. Ukrywa się tam inny obiekt. Może być konieczne utworzenie innego obiektu lub struktury danych zawierającej te parametry. Więcej informacji można znaleźć w tym artykule na temat wzorca obiektu parametru .

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:

  • Ułatwia czytanie kodu. Osobiście uważam, że o wiele łatwiej jest przeczytać „listę reguł” złożoną ze ifstruktury, która wywołuje wiele metod o opisowych nazwach, niż ze struktury, która robi to wszystko w jednej metodzie.
  • Jest bardziej testowalny jednostkowo. Twój problem został podzielony na kilka mniejszych zadań, które są indywidualnie bardzo proste. Zbiór testów jednostkowych składałby się następnie z zestawu testów behawioralnych, który sprawdza ścieżki za pomocą metody master oraz zbiór mniejszych testów dla każdej pojedynczej procedury.
Michael K.
źródło
5
Abstrakcja parametrów została przekształcona w wzorzec projektowy? Co się stanie, jeśli masz 3 klasy parametrów. Czy dodajesz jeszcze 9 przeciążeń metod, aby obsłużyć różne możliwe kombinacje parametrów? To brzmi jak nieprzyjemny problem z deklaracją parametru O (n ^ 2). Och, czekaj, możesz odziedziczyć tylko 1 klasę w Javie / C #, więc to będzie wymagało trochę więcej biopłytek (może trochę więcej podklas), aby działało w praktyce. Przepraszam, nie jestem przekonany. Ignorowanie bardziej ekspresyjnego podejścia, które język może zaoferować na rzecz złożoności, jest po prostu błędne.
Evan Plaice,
Chyba że użyjesz wzorca Object Pattern do spakowania zmiennych w instancji obiektu i przekazania go jako parametru. Działa to w przypadku pakowania, ale może być kuszące, aby stworzyć klasy różnych zmiennych dla wygody uproszczenia definicji metody.
Evan Plaice,
@EvanPlaice Nie mówię, że musisz używać tego wzorca, gdy masz więcej niż jeden parametr - masz całkowitą rację, że staje się jeszcze bardziej nieprzyjemny niż pierwsza lista. Mogą wystąpić przypadki, w których naprawdę potrzebujesz dużej liczby parametrów i po prostu nie działa, aby zawinąć je w obiekt. Nie spotkałem się jeszcze z przypadkiem rozwoju przedsiębiorstwa, który nie mieścił się w jednym z dwóch segmentów, o których wspomniałem w mojej odpowiedzi - nie oznacza to jednak, że nie istnieje.
Michael K
@MichaelK Jeśli nigdy ich nie używałeś, spróbuj googlować w „inicjalizatorach obiektów”. Jest to dość nowatorskie podejście, które znacznie zmniejsza definicję płyty kotłowej. Teoretycznie można wyeliminować konstruktory klas, parametry i przeciążenia w jednym ujęciu. W praktyce jednak zwykle dobrze jest utrzymywać jeden wspólny konstruktor i polegać na składni „inicjalizatora obiektów” w przypadku pozostałych niejasnych / niszowych właściwości. IMHO, jest najbliżej ekspresji dynamicznie pisanych języków w języku pisanym statycznie.
Evan Plaice,
@Evain Plaice: Od kiedy dynamicznie pisane języki są ekspresyjne?
ThomasX
41

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:

Idealna liczba argumentów dla funkcji wynosi zero (niladic). Dalej jest jeden (monadyczny), a następnie dwa (dyadyczny). Tam, gdzie to możliwe, należy unikać trzech argumentów (triadycznych). Więcej niż trzy (poliady) wymagają bardzo specjalnego uzasadnienia - i i tak nie powinny być używane.

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:

LangDetector detector = new LangDetector(someText);
//lots of lines
String language = detector.detectLanguage();

vs.

LangDetector detector = new LangDetector();
//lots of lines
String language = detector.detectLanguage(someText);

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.

Renato Dinhani
źródło
8
„trzy w szczególnych przypadkach i cztery lub więcej, nigdy!” BS. Co powiesz na Matrix.Create (x1, x2, x3, x4, x5, x6, x7, x8, x9); ?
Łukasz Madon,
71
Zero jest idealne? W jaki sposób, do cholery, funkcje uzyskują informacje? Globalne / instancja / statyczne / jakiekolwiek zmienne? SZCZĘŚCIE
Peter C
9
To zły przykład. Odpowiedź brzmi oczywiście: 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.
Matthieu M.
8
@lukas, w językach obsługujących takie fantazyjne konstrukcje, jak tablice lub listy (dyszę!), co powiesz na Matrix.Create(input);gdzie input, powiedzmy, .NET IEnumerable<SomeAppropriateType>? W ten sposób nie potrzebujesz również osobnego przeciążenia, jeśli chcesz utworzyć matrycę zawierającą 10 elementów zamiast 9.
CVn
9
Zero argumentów jako „idealnych” to gówno i jeden z powodów, dla których uważam, że Clean Code jest zdecydowanie zawyżony.
user949300,
24

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,

3rdGradeClass.finishHomework(int lessonId) {
    result = students.assignHomework(lessonId, dueDate);
    teacher.verifyHomeWork(result);
}

To jest proste.

Jeśli nie masz poprawnego modelu, metoda będzie następująca

Manager.finishHomework(grade, students, lessonId, teacher, ...) {
    // This is not good.
}

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.

java_mouse
źródło
16

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 r0do r3i 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ść ( r0do r3moż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.

  1. Inlining powoduje, że skompilowany plik binarny rośnie, ponieważ ten sam kod jest powielany w formie binarnej, jeśli jest wywoływany z wielu miejsc. Jest to szkodliwe, jeśli chodzi o użycie pamięci podręcznej I.
  2. Kompilatory zwykle pozwalają na inliniowanie tylko do pewnego poziomu (3 kroki IIRC?). Wyobraź sobie, że wywołujesz funkcję wbudowaną z funkcji wbudowanej z funkcji wbudowanej. Rozwój binarny eksplodowałby, gdyby inlinewe wszystkich przypadkach był traktowany jako obowiązkowy.
  3. Istnieje wiele kompilatorów, które całkowicie zignorują inlinelub faktycznie dadzą ci błędy, gdy je napotkają.
Lew
źródło
To, czy przekazanie dużej liczby parametrów jest dobre czy złe z punktu widzenia wydajności, zależy od alternatyw. Jeśli metoda potrzebuje tuzina informacji, a ktoś będzie ją nazywał setki razy z tymi samymi wartościami dla jedenastu z nich, zastosowanie metody przez tablicę może być szybsze niż przyjęcie kilkunastu parametrów. Z drugiej strony, jeśli każde wywołanie będzie wymagało unikalnego zestawu dwunastu wartości, tworzenie i zapełnianie tablicy dla każdego wywołania może być łatwiejsze niż zwykłe przekazywanie wartości bezpośrednio.
supercat
Czy wkładanie nie rozwiązuje tego problemu?
KjMag,
@KjMag: Tak, do pewnego stopnia. Ale istnieje wiele gotchas w zależności od kompilatora. Funkcje będą zwykle wstawiane tylko do pewnego poziomu (jeśli wywołasz funkcję wstawioną, która wywołuje funkcję wstawioną, która wywołuje funkcję wbudowaną ....). Jeśli funkcja jest duża i jest wywoływana z wielu miejsc, wstawianie wszędzie powoduje, że plik binarny jest większy, co może oznaczać więcej braków w pamięci podręcznej I. Inklinacja może więc pomóc, ale to nie jest srebrna kula. (Nie wspominając o tym, że istnieje kilka starych wbudowanych kompilatorów, które nie obsługują inline.)
Leo
7

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.

James Anderson
źródło
Zgadzam się, obiekt kontekstowy może okazać się bardzo przydatny, gdy struktury parametrów funkcji stają się dość złożone. Niedawno napisałem na blogu o używaniu obiektów kontekstowych z wzorem przypominającym odwiedzającego
Lukas Eder
5

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:

// Example definition
def example_function(normalParam, args*, kwargs**):
  for i in args:
    print 'args' + i + ': ' + args[i] 
  for key in kwargs:
    print 'keyword: %s: %s' % (key, kwargs[key])
  somevar = kwargs.get('somevar','found')
  missingvar = kwargs.get('somevar','missing')
  print somevar
  print missingvar

// Example usage

    example_function('normal parameter', 'args1', args2, 
                      somevar='value', missingvar='novalue')

Wyjścia:

args1
args2
somevar:value
someothervar:novalue
value
missing

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:

$.ajax({
  type: 'GET',
  url: 'http://someurl.com/feed',
  data: data,
  success: success(),
  error: error(),
  complete: complete(),
  dataType: 'jsonp'
});

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 # .

Evan Plaice
źródło
3
Nie jestem pewien, czy podoba mi się ta metoda, głównie dlatego, że tracisz wartość samokontroli poszczególnych parametrów. W przypadku list podobnych elementów ma to sens (na przykład metoda, która pobiera listę ciągów i konkatenuje je), ale dla dowolnego zestawu parametrów jest to gorsze niż długie wywołanie metody.
Michael K
@MichaelK Ponownie spójrz na inicjalizatory obiektów. Pozwalają one jawnie zdefiniować właściwości w przeciwieństwie do sposobu, w jaki są one domyślnie zdefiniowane w tradycyjnych parametrach metody / funkcji. Przeczytaj to, msdn.microsoft.com/en-us/library/bb397680.aspx , aby zobaczyć, co mam na myśli.
Evan Plaice,
3
Utworzenie nowego typu tylko do obsługi listy parametrów brzmi dokładnie tak, jak definicja niepotrzebnej złożoności ... Jasne, dynamiczne języki pozwalają ci tego uniknąć, ale wtedy dostajesz pojedynczą kulkę parametru goo. Niezależnie od tego nie odpowiada to na zadane pytanie.
Telastyn
@Telastyn O czym ty mówisz? Nie utworzono nowego typu, deklarujesz właściwości bezpośrednio, używając składni literału obiektu. To jak definiowanie anonimowego obiektu, ale metoda interpretuje go jako grupowanie parametrów klucz = wartość. To, na co patrzysz, to tworzenie instancji metody (a nie obiekt enkapsulujący parametr). Jeśli twoja wołowina ma opakowanie parametrów, spójrz na wzorzec obiektu parametru wymieniony w jednym z pozostałych pytań, ponieważ dokładnie tak jest.
Evan Plaice,
@EvanPlaice - Z wyjątkiem tego, że statyczne języki programowania ogólnie wymagają (często nowego) deklarowanego typu, aby umożliwić wzorzec obiektu parametru.
Telastyn
3

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.

Telastyn
źródło
2

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:

<?php

createBlogPost('the title', 'the summary', 'the author', 'the date of publication, 'the keywords', 'the category', 'etc');

?>

Idź po:

<?php

// create a hash of post data
$post_data = array(
  'title'    => 'the title',
  'summary'  => 'the summary',
  'author'   => 'the author',
  'pubdate'  => 'the publication date',
  'keywords' => 'the keywords',
  'category' => 'the category',
  'etc'      => 'etc',
);

// and pass it to the appropriate function
createBlogPost($post_data);

?>

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.

Chris Allen Lane
źródło
Widząc ten post, podkreślam kolejną zaletę metody pass-a-hash: zauważ, że mój pierwszy przykład kodu jest tak długi, że generuje pasek przewijania, podczas gdy drugi pasuje idealnie na stronie. To samo będzie prawdopodobnie dotyczyć edytora kodu.
Chris Allen Lane
0

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.

Billal Begueradj
źródło
0

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:

Argumenty są trudne. Biorą dużo mocy pojęciowej. Dlatego pozbyłem się prawie wszystkich z tego przykładu. Rozważmy na przykład StringBufferprzykład. Moglibyśmy przekazać go jako argument, a nie uczynić go zmienną instancji, ale wtedy nasi czytelnicy musieliby go interpretować za każdym razem, gdy go widzieli. Kiedy czytasz historię opowiedzianą przez moduł, includeSetupPage() łatwiej ją zrozumieć niż includeSetupPageInto(newPageContent). Argument jest na innym poziomie abstrakcji, że nazwa funkcji zmusza cię do poznania szczegółów (innymi słowy StringBuffer), które nie są szczególnie ważne w tym momencie.

Na includeSetupPage()powyższym przykładzie poniżej znajduje się niewielki fragment jego refaktoryzowanego „czystego kodu” na końcu rozdziału:

// *** NOTE: Commments are mine, not the author's ***
//
// Java example
public class SetupTeardownIncluder {
    private StringBuffer newPageContent;

    // [...] (skipped over 4 other instance variables and many very small functions)

    // this is the zero-argument function in the example,
    // which calls a method that eventually uses the StringBuffer instance variable
    private void includeSetupPage() throws Exception {
        include("SetUp", "-setup");
    }

    private void include(String pageName, String arg) throws Exception {
        WikiPage inheritedPage = findInheritedPage(pageName);
        if (inheritedPage != null) {
            String pagePathName = getPathNameForPage(inheritedPage);
            buildIncludeDirective(pagePathName, arg);
        }
    }

    private void buildIncludeDirective(String pagePathName, String arg) {
        newPageContent
            .append("\n!include ")
            .append(arg)
            .append(" .")
            .append(pagePathName)
            .append("\n");
    }
}

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).

synek
źródło
-2

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ś.

Michael Durrant
źródło
12
Jeśli funkcja ma zero parametrów, albo zwraca stałą wartość (przydatną w niektórych okolicznościach, ale raczej ograniczającą), albo używa jakiegoś ukrytego stanu, który lepiej byłoby wyjaśnić. (W wywołaniach metod OO obiekt kontekstu jest na tyle wyraźny, że nie powoduje problemów.)
Donal Fellows
4
-1 za brak zacytowania źródła
Joshua Drake
Mówisz poważnie, że idealnie wszystkie funkcje nie wymagałyby parametrów? A może to hiperbola?
GreenAsJade
1
Zobacz argument wuja Boba pod adresem: informit.com/articles/article.aspx?p=1375308 i zauważ, że na dole mówi: „Funkcje powinny mieć małą liczbę argumentów. Żaden argument nie jest najlepszy, a następnie jeden, dwa i trzy Ponad trzy są bardzo wątpliwe i należy ich unikać z uprzedzeniami. ”
Michael Durrant
Podałem źródło. Śmieszne bez komentarzy od tego czasu. Próbowałem również odpowiedzieć na część z wytycznymi, ponieważ wielu uważa teraz wujka Boba i Clean Code za wytyczne. Ciekawe, że niezwykle pozytywna odpowiedź (obecnie) mówi, że nie znam żadnych wytycznych. Wujek Bob nie zamierzał być autorytatywny, ale tak jest i ta odpowiedź przynajmniej stara się być odpowiedzią na specyfikę pytania.
Michael Durrant