Jakie są najlepsze praktyki dotyczące zamawiania parametrów w funkcji?

52

Czasami (rzadko) wydaje się, że najlepszym rozwiązaniem jest utworzenie funkcji, która wymaga przyzwoitej liczby parametrów. Jednak kiedy to robię, czuję, że często losowo wybieram porządkowanie parametrów. Zazwyczaj stosuję „porządek ważności”, z najważniejszym parametrem na początku.

Czy jest na to lepszy sposób? Czy istnieje sposób „porządkowania” najlepszych praktyk, który poprawia przejrzystość?

Casey Patton
źródło
5
Nazwane parametry sprawiają, że jest to mniejszy problem. Python prowadzi to do końca, spójrz na to. Dobrym przykładem jest C # - wiele sposobów wywoływania MessageBox.Show. Spójrz również na to.
Job
8
W Haskell możliwa przydatność zastosowania funkcji częściowej zwykle sugeruje naturalny porządek argumentów.
tdammers
Co uważasz za przyzwoitą liczbę parametrów?
Chris,
Myślałem> 2. Myślę, że porządkowanie dotyczy jednak 2 parametrów.
Casey Patton,
3
@tdammers dotyczy to również każdego języka, który ma bezpośrednie wsparcie dla curry
jk.

Odpowiedzi:

55

Ogólnie: użyj go .

Napisz test dla swojej funkcji, test w prawdziwym świecie.
Coś , co naprawdę chciałbyś zrobić z tą funkcją .

I zobacz, w jakiej kolejności je odłożyłeś.

Chyba że masz już (lub wiesz) o niektórych funkcjach, które robią coś podobnego.
W takim przypadku: dostosuj się do tego, co już robią, przynajmniej w przypadku pierwszych argumentów.

np. Czy wszystkie przyjmują dokument / obiekt / wskaźnik pliku / serię wartości / współrzędne jako pierwszy argument (y)? Na miłość boską, dostosuj się do tych argumentów .

Unikaj mylenia swoich współpracowników i przyszłego siebie .

ZJR
źródło
12
„Unikaj mylenia… twoje przyszłe ja” brzmi jak ogólnie dobra filozofia.
Jeanne Pindar
28

Zwykle stosuję się do tych zasad, choć nie zawsze z takim samym pierwszeństwem. Wydaje mi się, że jest to teraz automatyczny proces myślowy i nie przesadzam z tym, z wyjątkiem projektu publicznego interfejsu API .

Lejek wyboru

  1. Semantyka
  2. Znaczenie / trafność
  3. Częstotliwość użycia
  4. Obawy we / wy

1. Najpierw semantyka

Zwłaszcza w OOP wybierz parametry na podstawie ich znaczenia semantycznego dla akcji lub komunikatu. Podpis dobrze nazwanej metody z dobrze nazwanym parametrem powinien:

  • czuć się naturalnie dzwonić,
  • być samoopisujące pod względem zamiarów i zachowania.

(Z tych powodów czasami użycie niestandardowych typów lub aliasów zamiast prymitywów może zwiększyć ekspresję podpisu).

2. Następnie znaczenie

Najbardziej „znaczący” parametr jest pierwszy (lub następny ...)

3. Następnie częstotliwość

Częstotliwość ma również znaczenie, szczególnie w języku, w którym nie masz nazwanych parametrów, ale możesz mieć domyślne wartości parametrów pozycyjnych. Oznacza to, że kolejność parametrów nie zmienia się, i oczywiście nie można ustawić parametrów N + 1, jeśli chcesz wymusić domyślną wartość N-tego parametru (z wyjątkiem sytuacji, gdy twój język ma pojęcie parametru zastępczego ).

Dobrą wiadomością jest to, że zwykle częstotliwość jest ważna, więc idzie to w parze z poprzednim punktem. A potem prawdopodobnie od Ciebie zależy, czy stworzysz API, aby miał odpowiednią semantykę.

4. Nie zapominajmy o We / Wy

jeśli twoja metoda / funkcja pobiera dane wejściowe i generuje dane wyjściowe, a te ostatnie nie mają być „zwracane” (za pomocą instrukcji return) ani „wyrzucane” (za pomocą systemu wyjątków), wówczas istnieje możliwość przekazania wartości z powrotem do dzwoniącego przy użyciu innych parametrów (lub parametru wejściowego). Odnosi się to do semantyki iw większości przypadków sensowne jest, aby pierwsze parametry definiowały dane wyjściowe, a ostatnie parametry otrzymywały dane wyjściowe.

Ponadto innym podejściem mającym mniej parametrów i maksymalizację semantyki byłoby zastosowanie podejścia funkcjonalnego lub zdefiniowanie wzorca konstruktora , dzięki czemu można wyraźnie zestawiać dane wejściowe, definiować dane wyjściowe i wyszukiwać je w razie potrzeby.

(Zauważ, że nie wspominam o zmiennych globalnych, ponieważ dlaczego miałbyś użyć jednej, prawda?)


Kilka rzeczy do rozważenia

  • Użyteczność

    Większość z powyższych pokaże się naturalnie, jeśli zastosujesz się do rad ZJR : użyj go!

  • Zastanów się nad refaktoryzacją

    Jeśli martwisz się o porządkowanie parametrów, być może to zmartwienie znajduje swoje źródło w powyższym oraz w tym, że twój interfejs API jest źle zaprojektowany. Jeśli masz zbyt wiele parametrów, coś najprawdopodobniej można skomponować / zmodularyzować i zrefaktoryzować .

  • Zastanów się nad wydajnością

    Należy pamiętać, że implementacje niektórych języków będą miały bardzo istotny wpływ na zarządzanie pamięcią środowiska wykonawczego podczas używania parametrów. Stąd powód, dla którego podręczniki stylu wielu języków zalecają, aby lista parametrów była prosta i krótka . Na przykład przy maks. 4 parametrach. Zostawiam to jako ćwiczenie, aby dowiedzieć się, dlaczego.

  • Odpowiedź Bevana i wzmianka o zaleceniach Clean Code są również bardzo istotne!

Haylem
źródło
18

Z szacunkiem podam, że martwienie się o porządkowanie parametrów martwi się o niewłaściwą rzecz.

W książce wuja Boba „ Czysty kod ” przekonująco opowiada się za tym, aby metody nigdy nie miały więcej niż dwóch argumentów - a większość powinna mieć tylko jeden, jeśli w ogóle. W takim przypadku zamówienie jest oczywiste lub nieważne.

Jednak niedoskonale staram się postępować zgodnie z radą wuja Boba - i to poprawia mój kod.

W rzadkich przypadkach, gdy metoda wydaje się wymagać więcej informacji, dobrym pomysłem jest wprowadzenie obiektu parametru . Zwykle uważam, że jest to pierwszy krok w kierunku odkrycia nowej koncepcji (obiektu) kluczowej dla mojego algorytmu.

Bevan
źródło
Zdecydowanie się z tobą zgadzam! Moje pierwsze stwierdzenie miało na celu stwierdzenie, że nie jest to dla mnie typowe, haha. Ale w przypadku, gdy uważam, że naprawdę muszę przekazać niektóre parametry i nie warto refaktoryzować całego programu, zastanawiam się nad zamówieniem.
Casey Patton,
3
kaszel PHP kaszel . Przepraszam - mówiłeś coś o tym, aby nie martwić się kolejnością parametrów?
Craige
Czy nie będziesz miał po prostu konstruktora dla swojego parametru parametru, który przyjmuje tyle argumentów?
detly
Nie - ponieważ obiekt parametru można „zbudować” na kilku instrukcjach w sposób bardziej czytelny.
Bevan
2
@Bevan: jest to bardzo dyskusyjne. Budowanie obiektów w ten sposób oznacza, że ​​nie mogą już być niezmienne; utrzymywanie obiektów w niezmiennej postaci, gdy tylko jest to możliwe, to kolejny świetny sposób na zwiększenie konserwacji.
tdammers,
10

Staram się umieścić parametry IN na pierwszym miejscu, parametry OUT na drugim miejscu. Istnieje również pewne naturalne uporządkowanie, np. createPoint(double x, double y)Jest zdecydowanie lepsza niż createPoint(double y, double x).

quant_dev
źródło
5
Czasami lepiej jest na odwrót, np. Gdy zawsze jest tylko jeden argument OUT i zmienna liczba argumentów in, np addAllTo(target, something1, something2). In .
maaartinus,
Chociaż przestrzegam tej samej zasady, staram się unikać parametrów OUT, gdy tylko jest to możliwe, a także większości dobrych programistów. Zatem liczba funkcji, w przypadku których to zalecenie rzeczywiście pomaga PO, powinna być raczej niewielka.
Doc Brown,
7
@DocBrown Naprawdę nie powinieneś unikać dobrych programistów, mogą się przydać.
RyanfaeScotland
@RyanfaeScotland: cholera, powinienem przeczytać moje komentarze dwa razy, zanim je opublikuję (a przynajmniej w ciągu pięciu minut mogę je zmienić) ;-)
Doc Brown
6

Nigdy nie widziałem udokumentowanej „najlepszej praktyki” dotyczącej tego konkretnego tematu, ale moim osobistym standardem jest wymienienie ich w kolejności, w jakiej pojawią się w metodzie, w której są używane, lub jeśli metoda jest bardziej przechodząc do warstwy danych wymienię je w kolejności, w jakiej pojawią się w schemacie db lub metodach warstwy danych.

Ponadto, gdy występuje wiele przeciążeń metody, zauważam, że typowym sposobem jest wylistowanie ich, zaczynając od parametrów wspólnych dla wszystkich (lub większości) metod, przy czym każda inna metoda jest dołączana na końcu dla każdego przeciążenia metody, np. :

void func1(string param) { }
void func2(string param, int param2) { }
void func3(string param, string param3) { }
void func3(string param, int param2, string param3) { }
Joel Etherton
źródło
6

Kolejność: dane wejściowe, dane wyjściowe, parametry opcjonalne.

Jonathan Khoo
źródło
3

Często stosuję się do konwencji C / C ++ polegającej na umieszczaniu constparametrów w pierwszej kolejności (tj. Parametrów przekazywanych przez wartość), a następnie tych, które przekazujesz przez odniesienie. To niekoniecznie musi być poprawna metoda wywoływania funkcji, ale jeśli jesteś zainteresowany sposobem, w jaki każdy kompilator obsługuje parametry, spójrz na poniższe linki do reguł rządzących i / lub kolejności, w której parametry są wypychane na stos.

http://msdn.microsoft.com/en-us/library/zthk2dkh%28v=vs.80%29.aspx

http://en.wikipedia.org/wiki/Calling_convention

Rachunek
źródło
Innym linkiem, który może Cię zainteresować, jest msdn.microsoft.com/en-us/library/984x0h58%28v=vs.71%29.aspx Aby zapoznać się z funkcjami rekurencyjnymi, zobacz poniższy link. publications.gbdirect.co.uk/c_book/chapter4/... Nie to, że zapomniałem, ale będąc nowym użytkownikiem, nie mogę opublikować komentarza zawierającego więcej niż dwa linki ... to frustrujące!
Bill
1

Zwykle po prostu wybieram kolejność parametrów „co wygląda mniej cyprycznie”. Im mniej razy muszę przejść do definicji metody / funkcji, tym lepiej. I miło jest mieć nazwane parametry opisujące, do czego są używane, w ten sposób, gdy pojawia się mała podpowiedź (VS), to czyni to jeszcze łatwiejszym.

Jeśli masz linie i linie parametrów, możesz rozważyć inny projekt. Cofnij się i zobacz, jak możesz podzielić to na więcej funkcji / metod. To tylko pomysł, ale kiedy mam kilkanaście parametrów w swojej funkcji, prawie zawsze nie jest to problem z parametrami, ale problem z projektem.


źródło
2
„cyprtic”: świetny sposób na podkreślenie.
Bevan
2
Nigdy nie miałeś literówki @Bevan?
1
Cały czas mam literówki - pisanie w języku cypricznym było tak dobre, że myślałem, że celowo . Proszę, zmień to z powrotem, to pomaga w zrozumieniu, nawet jeśli był to wypadek.
Bevan
1
@Bevan, haha ​​ok, rozumiem co mówisz. Słuszna uwaga! Zmieniono z powrotem =)
1

Czasami (rzadko) wydaje się, że najlepszym rozwiązaniem jest utworzenie funkcji, która wymaga przyzwoitej liczby parametrów.

Używanie kilku parametrów jest często wyraźnym wskaźnikiem, że naruszasz SRP w tej metodzie. Metoda, która wymaga wielu parametrów, nie może zrobić tylko jednej rzeczy. Excpetion może być funkcją matematyczną lub metodą konfiguracji, w której rzeczywiście potrzebnych jest kilka parametrów . Unikałbym wielu parametrów, ponieważ diabeł unika wody święconej. Im więcej parametrów używasz w metodzie, tym większa szansa, że ​​metoda jest (zbyt) złożona; tym bardziej złożona oznacza: trudniejsza w utrzymaniu i jest to mniej pożądane.

Jednak kiedy to robię, czuję, że często losowo wybieram porządkowanie parametrów. Zazwyczaj stosuję „porządek ważności”, z najważniejszym parametrem na początku.

Zasadniczo wybierasz losowo . Oczywiście możesz pomyśleć, że parametr A jest bardziej odpowiedni niż parametr B ; ale może nie być tak w przypadku użytkowników interfejsu API, którzy uważają B za najbardziej odpowiedni parametr. Więc nawet jeśli byłeś uważny w wyborze kolejności - dla innych może się to wydawać przypadkowe .

Czy jest na to lepszy sposób? Czy istnieje sposób „porządkowania” najlepszych praktyk, który poprawia przejrzystość?

Istnieje kilka sposobów wyjścia:

a) Trywialny przypadek: nie używaj więcej niż jednego parametru.

b) Ponieważ nie określiłeś, jaki język wybrałeś, istnieje szansa, że ​​wybrałeś język o nazwanych parametrach . Jest to ładny cukier syntaktyczny, który pozwala rozluźnić znaczenie kolejności parametrów:fn(name:"John Doe", age:36)

Nie każdy język pozwala na takie subtelności. Co wtedy?

c) Możesz użyć Dictionary / Hashmap / Associative Array jako parametru: np. Javascript pozwala na: fn({"name":"John Doe", age:36})co nie jest daleko od (b).

d) Oczywiście, jeśli pracujesz ze statycznie typowanym językiem, takim jak Java. możesz użyć Hashmapy , ale stracisz informacje o typie (np. podczas pracy HashMap<String, Object>), gdy parametry mają różne typy (i trzeba rzutować).

Następnym logicznym krokiem byłoby przekazanie Object(jeśli używasz Javy) odpowiednich właściwości lub czegoś bardziej lekkiego jak struct (jeśli napiszesz np. C # lub C / C ++).

Praktyczna zasada:

1) Najlepszy przypadek - twoja metoda wcale nie wymaga żadnych parametrów

2) Dobry przypadek - twoja metoda wymaga jednego parametru

3) Dopuszczalny przypadek - twoja metoda wymaga dwóch parametrów

4) Wszystkie pozostałe przypadki powinny zostać ponownie rozpatrzone

Thomas Junk
źródło
0

Często złożony obiekt jako parametr jest lepszy - wersja nazwanych parametrów biednego człowieka, która działa na większości platform. I otwiera drzwi do parametrów z zachowaniem do uruchomienia.

Jako przykład większości rzeczy, których nie powinieneś robić, możesz spróbować przeczytać standardową dokumentację biblioteki PHP.

Wyatt Barnett
źródło
0

Zwykle zamawiam je najpierw jako wymagane, a nie według jakiejś połączonej miary ważności i częstotliwości używania zgodnie z „odczuciem” (może być postrzegane jako ORDER BY required DESC, SOME_MAGIC_FEELING(importancy,frequency)), a nie zgodnie z żadną konkretną praktyką.

Jednak, jak zauważyli inni, myślę, że podstawowym problemem, który sprawia, że ​​jest to problem, jest użycie zbyt wielu parametrów (IMHO, cokolwiek> 3 to zbyt wiele) i to jest prawdziwy problem, którym powinieneś się zająć. Na blogu rebecca murphey znajduje się ciekawy post na ten temat.

Myślę, że gdy masz tylko 1-3 argumenty, prawidłowe uporządkowanie jest dość oczywiste i po prostu „czujesz”, co jest właściwe.

shesek
źródło
0

Podobnie do odpowiedzi @Wyatt Barnetts, cokolwiek więcej niż kilka parametrów lub bardzo wyraźne parametry dla metody, polecam zamiast tego przekazać obiekt. Zazwyczaj jest to łatwiejsze do aktualizacji / konserwacji, bardziej czytelne i eliminuje konieczność martwienia się o zamówienie . Ponadto zbyt wiele parametrów dla metody jest zapachem kodu i istnieją wspólne wzorce refaktoryzacji, które można zastosować, aby pomóc to poprawić.

Jawny przykład:

public int add(int left, int right)
{
  return left + right;
}

Ponieważ jest to dość jasno określony przykład, a dodawanie jest przemienne (kolejność nie ma znaczenia), po prostu idź z nim.

Jeśli jednak dodasz coś bardziej złożonego:

public SomeComplexReturnValue Calculate(int i, float f, string s, object o)
{
  // do work here
}

Stanie się:

public class SomeComplexInputForCalculation
{
  public int i; 
  public float f;
  public string s; 
  public object o;
}

public SomeComplexReturnValue Calculate(SomeComplexInputForCalculation input)
{
  // do work here
}

Mam nadzieję że to pomoże...

longda
źródło
0

Zamów to w dowolny sposób, w jaki według ciebie curry najprawdopodobniej skorzysta. Na przykład najpierw parametry funkcji.

alternatywny
źródło
0

„Ważne przede wszystkim” nie znaczy wiele. Istnieje kilka konwencji.

Jeśli przekażesz obiekt wywołujący (często nazywany nadawcą), jest to pierwszy.

Na liście powinna być pewna płynność , co oznacza, że ​​powinieneś wiedzieć, o czym jest argument, zanim go przeczytasz. Przykład:

CopyFolder (ścieżka ciągu, rekurencyjny bool);

Jeśli na pierwszym miejscu byłoby rekurencyjne, byłoby to mylące, ponieważ nie ma jeszcze kontekstu. Jeśli już teraz chodzi o kopiowanie (1) folderu (2), wówczas argument rekurencyjny zaczyna mieć sens.

Dzięki temu funkcje podobne do IntelliSense działają dobrze: użytkownik uczy się, wypełniając argumenty, budując zrozumienie metody i jej działania. Podobnie przeciążenia powinny zachować tę samą kolejność dla równych części.

Wtedy nadal możesz znaleźć argumenty, które są „równe” pod tym względem i możesz ulec pokusie, aby kolejność zależała od rodzaju argumentu. Tylko dlatego, że kilka strun pod rząd, a potem kilka boolean wygląda ładniej. Na pewnym poziomie stanie się to raczej sztuką niż umiejętnością.

Nie dbam zbytnio o stwierdzenie, że powinieneś zawinąć wszystkie argumenty w obiekt, aby otrzymać jeden argument. To tylko oszukanie samego siebie (nie sprawia, że ​​metoda jest mniej skomplikowana, nadal ma wszystkie te argumenty, po prostu je ukryłeś). Może to nadal być wygodne, jeśli prześlesz zestaw od metody do metody kilka razy, refaktoryzacja stanie się znacznie łatwiejsza, ale pod względem projektowym udajesz, że robisz różnicę. To nie tak, że skończysz z sensownym obiektem, który coś reprezentuje, będzie to tylko garść argumentów, niczym nie różniących się od listy w deklaracji metody. Nie sprawi, że wujek Bob będzie szczęśliwy.

Martin Maat
źródło