Dlaczego wiele języków nie obsługuje nazwanych parametrów? [Zamknięte]

14

Właśnie myślałem, o ile łatwiej byłoby odczytać kod, jeśli wywołując funkcję, możesz napisać:

doFunction(param1=something, param2=somethingElse);

Nie mogę wymyślić żadnych wad, dzięki czemu kod byłby znacznie bardziej czytelny. Wiem, że możesz przekazać tablicę jako jedyny argument i mieć klucze tablicy jako nazwy parametrów, jednak nadal nie byłoby to tak czytelne.

Czy brakuje mi tego wady? Jeśli nie, dlaczego wiele języków na to nie pozwala?


źródło
1
czy możesz podać język, który powinien (i może) nazywać parametry, ale go nie ma?
Bryan Chen
1
Myślę, że w wielu przypadkach jest to zbyt szczegółowe. O ile widziałem, to, czy język tego używa, czy nie, zależy od tego, czy wpłynie to pozytywnie, czy negatywnie. Większość języków w stylu C radzi sobie bez niego dobrze. W ich przypadku często jest dość oczywiste, co się dzieje, a jego brak naprawdę pomaga po prostu ograniczyć bałagan w ogóle.
Panzercrisis
2
@SteveFallows: „Nazwany parametr parametru” wydaje się dziwną nazwą dla definicji płynnego API (nazwanego przez Martina Fowlera) w klasie konstruktora. Przykłady tego samego wzoru można znaleźć w jednym z pierwszych elementów Effective Java Joshua Blocha .
jhominal
3
To niekoniecznie jest pytanie oparte na
opiniach
2
Zgoda. „Dlaczego wiele języków nie obsługuje nazwanych parametrów?” jest bardziej kwestią historii języków niż opinii. Byłoby opinia, gdyby ktoś zapytał go jako „Czy parametry nie mają lepszych nazw?”.
Andy Fleming,

Odpowiedzi:

14

Podanie nazw parametrów nie zawsze sprawia, że ​​kod jest bardziej czytelny: na przykład, czy wolałbyś czytać min(first=0, second=1)czy min(0, 1)?

Jeśli zaakceptujesz argument z poprzedniego akapitu, oczywiste jest, że określenie nazw parametrów nie powinno być obowiązkowe. Dlaczego we wszystkich językach podawanie nazw parametrów nie jest prawidłową opcją?

Mogę wymyślić co najmniej dwa powody:

  • Ogólnie rzecz biorąc, wprowadzenie drugiej składni dla już zaspokojonej potrzeby (tutaj, przekazywanie parametrów) jest czymś, czego korzyści należy zrównoważyć z nieodłącznym kosztem wprowadzonej złożoności języka (zarówno w implementacji języka, jak i w modelu umysłu języka programisty) ;
  • Sprawia, że ​​zmiana nazw parametrów jest zmianą API, która może nie być zgodna z oczekiwaniami dewelopera, że ​​zmiana nazwy parametru jest banalną, niełamującą zmianą;

Zauważ, że nie znam żadnego języka, który implementuje nazwane parametry bez implementacji parametrów opcjonalnych (które wymagają nazwanych parametrów) - więc powinieneś być ostrożny, przeceniając ich zalety w zakresie czytelności, które można uzyskać bardziej spójnie z definicją płynnych interfejsów .

jhominal
źródło
6
Regularnie piszę w kilku różnych językach, prawie zawsze muszę wyszukiwać parametry prostego podłańcucha w danym języku. Czy jest to podłańcuch (początek, długość) czy podłańcuch (początek, koniec) i czy koniec jest zawarty w ciągu?
James Anderson
1
@JamesAnderson - W jaki sposób podciąg parametrów nazwanych rozwiązałby problem? Nadal będziesz musiał sprawdzić parametry, aby wiedzieć, jak nazywa się drugi parametr (chyba że istnieją obie funkcje, a funkcja polimorfizmu języka pozwala na parametry tego samego typu o różnych nazwach).
mouviciel
6
@mouviciel: Nazwane parametry nie są zbyt przydatne dla pisarza , ale są fantastyczne dla czytelnika . Wszyscy wiemy, jak ważne są nazwy zmiennych dla tworzenia czytelnego kodu, a nazwane parametry pozwalają twórcy interfejsu na podawanie dobrych nazw parametrów, a także dobrych nazw funkcji, ułatwiając osobom używającym tego interfejsu pisanie czytelnego kodu.
Phoshi,
@Phoshi - masz rację. Właśnie zapomniałem o znaczeniu czytania kodu, kiedy pisałem mój poprzedni komentarz.
mouviciel
14

Nazwane parametry sprawiają, że kod jest łatwiejszy do odczytania, trudniejszy do napisania

Kiedy czytam fragment kodu, nazwane parametry mogą wprowadzić kontekst, który ułatwia zrozumienie kodu. Rozważmy na przykład tego konstruktora: Color(1, 102, 205, 170). Co to do diabła znaczy? Rzeczywiście, Color(alpha: 1, red: 102, green: 205, blue: 170)byłoby o wiele łatwiejsze do odczytania. Ale niestety, Kompilator mówi „nie” - chce Color(a: 1, r: 102, g: 205, b: 170). Pisząc kod przy użyciu nazwanych parametrów, spędzasz niepotrzebnie dużo czasu na wyszukiwaniu dokładnych nazw - łatwiej jest zapomnieć dokładne nazwy niektórych parametrów, niż zapomnieć o ich kolejności.

To raz mnie ugryzło, gdy korzystałem z DateTimeinterfejsu API, który miał dwie klasy rodzeństwa dla punktów i czasów trwania z prawie identycznymi interfejsami. Chociaż DateTime->new(...)zaakceptował second => 30argument, DateTime::Duration->new(...)poszukiwany seconds => 30i podobny dla innych jednostek. Tak, to absolutnie ma sens, ale pokazało mi to, że nazwane parametry - łatwość użycia.

Złe nazwy nawet nie ułatwiają czytania

Innym przykładem tego, jak nazwane parametry mogą być złe, jest prawdopodobnie język R. Ten fragment kodu tworzy wykres danych:

plot(plotdata$n, plotdata$mu, type="p", pch=17,  lty=1, bty="n", ann=FALSE, axes=FALSE)

Widać dwa argumenty pozycyjne dla x i y wierszy danych, a następnie na liście wymienionych parametrów. Istnieje wiele innych opcji z wartościami domyślnymi i tylko te są wymienione, których wartości domyślne chciałem zmienić lub wyraźnie określić. Kiedy zignorujemy to, że ten kod używa magicznych liczb i może skorzystać z wyliczeń (jeśli R miał jakiś!), Problem polega na tym, że wiele z tych parametrów jest raczej nieczytelnych.

  • pchjest w rzeczywistości znakiem wykresu, glifem, który zostanie narysowany dla każdego punktu danych. 17jest pustym okręgiem lub coś takiego.
  • ltyjest typem linii. Oto 1linia ciągła.
  • btyjest typem pudełka. Ustawienie tak, aby "n"uniknąć rysowania ramki wokół wykresu.
  • ann kontroluje wygląd adnotacji osi.

Dla kogoś, kto nie wie, co oznacza każdy skrót, opcje te są raczej mylące. Ujawnia to również, dlaczego R używa tych etykiet: Nie jako samodokumentujący się kod, ale (będąc językiem o dynamicznym typowaniu) jako klucze do mapowania wartości na ich poprawne zmienne.

Właściwości parametrów i podpisów

Podpisy funkcji mogą mieć następujące właściwości:

  • Argumenty można zamówić lub nieuporządkowane,
  • nazwany lub nienazwany,
  • wymagane lub opcjonalne.
  • Podpisy mogą być również przeciążone według rozmiaru lub typu,
  • i może mieć nieokreślony rozmiar z varargs.

Różne języki lądują na różnych współrzędnych tego systemu. W C argumenty są uporządkowane, nienazwane, zawsze wymagane i mogą być varargs. W Javie sytuacja jest podobna, tyle że podpisy mogą być przeciążone. W celu C podpisy są uporządkowane, nazwane, wymagane i nie mogą być przeciążone, ponieważ jest to tylko cukier składniowy w okolicach C.

Dynamicznie wpisywane języki z varargs (interfejsy wiersza poleceń, Perl,…) mogą emulować opcjonalne nazwane parametry. Języki z przeciążeniem rozmiaru sygnatury mają coś w rodzaju opcjonalnych parametrów pozycyjnych.

Jak nie implementować nazwanych parametrów

Myśląc o nazwanych parametrach, zwykle zakładamy nazwane, opcjonalne, nieuporządkowane parametry. Ich wdrożenie jest trudne.

Parametry opcjonalne mogą mieć wartości domyślne. Muszą być określone przez wywoływaną funkcję i nie powinny być wkompilowane w kod wywołujący. W przeciwnym razie wartości domyślne nie mogą zostać zaktualizowane bez ponownej kompilacji całego zależnego kodu.

Teraz ważnym pytaniem jest, w jaki sposób argumenty są przekazywane do funkcji. Przy uporządkowanych parametrach argumenty można przekazywać do rejestru lub w odpowiedniej kolejności na stosie. Kiedy chwilowo wykluczamy rejestry, problem polega na tym, jak umieścić na stosie nieuporządkowane argumenty opcjonalne.

W tym celu potrzebujemy trochę porządku w stosunku do opcjonalnych argumentów. Co jeśli kod deklaracji zostanie zmieniony? Ponieważ kolejność jest nieistotna, zmiana kolejności w deklaracji funkcji nie powinna zmieniać położenia wartości na stosie. Należy również rozważyć, czy możliwe jest dodanie nowego opcjonalnego parametru. Z perspektywy użytkownika wydaje się, że tak, ponieważ kod, który wcześniej nie używał tego parametru, powinien nadal działać z nowym parametrem. Wyklucza to więc porządkowanie, takie jak użycie kolejności w deklaracji lub użycie kolejności alfabetycznej.

Rozważ to również w świetle podtypu i zasady podstawienia Liskowa - w skompilowanym wyjściu te same instrukcje powinny móc wywoływać metodę na podtypie z możliwie nowymi nazwanymi parametrami i na nadtypie.

Możliwe implementacje

Jeśli nie możemy mieć ostatecznego porządku, potrzebujemy trochę nieuporządkowanej struktury danych.

  • Najprostszą implementacją jest po prostu przekazanie nazwy parametrów wraz z wartościami. W ten sposób nazwane parametry są emulowane w Perlu lub za pomocą narzędzi wiersza poleceń. To rozwiązuje wszystkie problemy z rozszerzeniami wspomniane powyżej, ale może być ogromnym marnotrawstwem miejsca - nie jest opcją w kodzie krytycznym dla wydajności. Ponadto przetwarzanie tych nazwanych parametrów jest teraz o wiele bardziej skomplikowane niż po prostu usuwanie wartości ze stosu.

    W rzeczywistości wymagania dotyczące miejsca można zmniejszyć, używając puli ciągów, co może zredukować późniejsze porównania ciągów do porównań wskaźników (z wyjątkiem sytuacji, gdy nie można zagwarantować, że ciągi statyczne są rzeczywiście połączone, w którym to przypadku oba ciągi będą musiały zostać porównane w Szczegół).

  • Zamiast tego moglibyśmy również przekazać sprytną strukturę danych, która działa jak słownik nazwanych argumentów. Jest to tanie po stronie dzwoniącego, ponieważ zestaw kluczy jest statycznie znany w miejscu połączenia. Pozwoliłoby to stworzyć idealną funkcję skrótu lub wstępnie obliczyć próbę. Odbiorca nadal będzie musiał sprawdzić, czy istnieją wszystkie możliwe nazwy parametrów, co jest nieco drogie. Coś takiego używa Python.

W większości przypadków jest to po prostu zbyt drogie

Jeśli funkcja o nazwanych parametrach ma być odpowiednio rozszerzalna, nie można przyjąć ostatecznego uporządkowania. Istnieją więc tylko dwa rozwiązania:

  • Ustaw kolejność nazwanych parametrów jako część podpisu i nie zezwalaj na późniejsze zmiany. Jest to przydatne w przypadku kodu samodokumentującego, ale nie pomaga w przypadku opcjonalnych argumentów.
  • Przekaż strukturę danych klucz-wartość do odbiorcy, który następnie musi wyodrębnić przydatne informacje. Jest to bardzo drogie w porównaniu i zwykle występuje tylko w językach skryptowych bez nacisku na wydajność.

Inne pułapki

Nazwy zmiennych w deklaracji funkcji mają zwykle jakieś wewnętrzne znaczenie i nie są częścią interfejsu - nawet jeśli wiele narzędzi dokumentacji nadal je pokaże. W wielu przypadkach potrzebujesz różnych nazw dla zmiennej wewnętrznej i odpowiedniego nazwanego argumentu. Języki, które nie pozwalają na wybranie widocznych z zewnątrz nazw nazwanych parametrów, nie zyskują wiele z nich, jeśli nazwa zmiennej nie jest używana w kontekście kontekstu wywoływania.

Problemem z emulacjami nazwanych argumentów jest brak statycznego sprawdzania po stronie wywołującej. Jest to szczególnie łatwe do zapomnienia, gdy przekazujesz słownik argumentów (patrząc na ciebie, Python). Jest to ważne, ponieważ przechodząc słownika jest częstym obejście, na przykład w JavaScript: foo({bar: "baz", qux: 42}). W tym przypadku ani typy wartości, ani istnienie lub brak niektórych nazw nie mogą być sprawdzane statycznie.

Emulowanie nazwanych parametrów (w językach o typie statycznym)

Po prostu użycie ciągów jako kluczy i dowolnego obiektu jako wartości nie jest zbyt przydatne w obecności sprawdzania typu statycznego. Nazwane argumenty można jednak emulować za pomocą struktur lub literałów obiektowych:

// Java

static abstract class Arguments {
  public String bar = "default";
  public int    qux = 0;
}

void foo(Arguments args) {
  ...
}

/* using an initializer block */
foo(new Arguments(){{ bar = "baz"; qux = 42; }});
amon
źródło
3
it is easier to forget the exact names of some parameters than it is to forget their order” ... to interesujące stwierdzenie.
Catskul,
1
Pierwszy kontrprzykład nie stanowi problemu z nazwanymi parametrami, a bardziej z wadą tego, jak angielskie liczby mnogie odwzorowują nazwy pól w interfejsach. Drugi kontrprzykład jest podobnie problemem, gdy programiści dokonują złych wyborów nazw. (Sam wybór interfejsu przekazywania parametrów nie będzie sam w sobie wystarczającym warunkiem czytelności - pytanie brzmi, czy jest to warunek konieczny lub pomoc w niektórych przypadkach).
mtraceur
1
+1 za ogólne punkty dotyczące kosztów wdrożenia i kompromisy z wykonaniem nazwanych parametrów. Myślę, że początek straci ludzi.
mtraceur
@mtraceur Masz rację. Teraz, 5 lat później, prawdopodobnie napisałbym odpowiedź zupełnie inaczej. Jeśli chcesz, możesz edytować moją odpowiedź, aby usunąć mniej pomocne rzeczy. (Zwykle takie zmiany byłyby odrzucane, ponieważ są sprzeczne z intencjami autora, ale tutaj wszystko jest w porządku). Na przykład teraz uważam, że wiele problemów z nazwanymi parametrami to tylko ograniczenia języków dynamicznych i powinny one po prostu uzyskać system typów. Np. JavaScript ma Flow i TypeScript, Python ma MyPy. Dzięki odpowiedniej integracji IDE, która eliminuje większość problemów z użytecznością. Nadal czekam na coś w Perlu.
amon
7

Z tego samego powodu, dla którego notacja węgierska nie jest już powszechnie stosowana; Intellisense (lub jego moralny odpowiednik w IDE innych niż Microsoft). Większość współczesnych IDE powie ci wszystko, co musisz wiedzieć o parametrze, po prostu najeżdżając kursorem na odwołanie do parametru.

To powiedziawszy, wiele języków obsługuje nazwane parametry, w tym C # i Delphi. W języku C # jest opcjonalny, więc nie musisz go używać, jeśli nie chcesz, i istnieją inne sposoby szczegółowego deklarowania członków, takie jak inicjalizacja obiektu.

Nazwane parametry są najbardziej przydatne, gdy chcesz określić tylko podzbiór parametrów opcjonalnych, a nie wszystkie. W języku C # jest to bardzo przydatne, ponieważ nie potrzebujesz już wielu przeciążonych metod, aby zapewnić programiście taką elastyczność.

Robert Harvey
źródło
4
Python używa nazwanych parametrów i jest to wspaniała funkcja języka. Chciałbym, żeby było więcej języków. Ułatwia domyślne argumenty, ponieważ nie jesteś już zmuszany, jak w C ++, do jawnego określania argumentów „przed” domyślnymi. (Tak jak w poprzednim akapicie, właśnie w ten sposób python unika braku przeciążania ... domyślne argumenty i argumenty nazwy pozwalają robić te same rzeczy.) Argumenty nazwane również zapewniają bardziej czytelny kod. Kiedy masz coś takiego sin(radians=2), nie potrzebujesz „Intellisense”.
Gort the Robot
2

Bash z pewnością obsługuje ten styl programowania, a zarówno Perl, jak i Ruby obsługują przekazywanie list parametrów nazwa / wartość (podobnie jak każdy język z natywną obsługą skrótów / map). Nic nie stoi na przeszkodzie, abyś zdefiniował strukturę / rekord lub skrót / mapę, która dołącza nazwy do parametrów.

W przypadku języków, które zawierają natywnie sklepy z hash / map / keyvalue, możesz uzyskać pożądaną funkcjonalność, po prostu przyjmując idiom do przekazywania skrótów klucz / wartość do twoich funkcji / metod. Po wypróbowaniu go w projekcie uzyskasz lepszy wgląd w to, czy coś zyskałeś, czy to ze zwiększonej wydajności wynikającej z łatwości użytkowania, czy z lepszej jakości dzięki zmniejszonej liczbie wad.

Zauważ, że doświadczeni programiści poczuli się komfortowo w przekazywaniu parametrów według zamówienia / gniazda. Może się również okazać, że ten idiom jest cenny tylko wtedy, gdy masz więcej niż kilka argumentów (powiedz> 5/6). A ponieważ duża liczba argumentów często wskazuje na (zbyt) złożone metody, może się okazać, że ten idiom jest korzystny tylko w przypadku najbardziej złożonych metod.

ChuckCottrill
źródło
2

Myślę, że to ten sam powód, dla którego c # jest bardziej popularny niż VB.net. Podczas gdy VB.NET jest bardziej „czytelny”, np. Wpisujesz „end if” zamiast nawiasu zamykającego, to po prostu jest więcej rzeczy, które wypierają kod i ostatecznie utrudniają zrozumienie.

Okazuje się, że to, co ułatwia zrozumienie kodu, to zwięzłość. Im mniej tym lepiej. Nazwy parametrów funkcji i tak są zwykle dość oczywiste i tak naprawdę nie pomagają zrozumieć kodu.

Rocklan
źródło
1
Gorąco nie zgadzam się z tym, że „mniej tym lepiej”. Jeśli chodzi o logiczną skrajność, zwycięzcy kodu golfowego byliby „najlepszymi” programami.
Riley Major
2

Nazwane parametry są rozwiązaniem problemu z refaktoryzacją kodu źródłowego i nie mają na celu uczynienia kodu źródłowego bardziej czytelnym . Nazwane parametry pomagają kompilatorowi / parserowi w rozwiązywaniu domyślnych wartości wywołań funkcji. Jeśli chcesz, aby kod był bardziej czytelny, niż dodawaj znaczące komentarze.

Języki, które są trudne do refaktoryzacji, często obsługują nazwane parametry, ponieważ foo(1)ulegają uszkodzeniu, gdy podpis foo()zmian, ale foo(name:1)jest mniej prawdopodobne, że pęknie, wymagając od programisty mniejszego wysiłku, aby wprowadzić zmiany foo.

Co robisz, gdy musisz wprowadzić nowy parametr do następującej funkcji, a ta funkcja zawiera setki lub tysiące wierszy kodu?

foo(int weight, int height, int age = 0);

Większość programistów wykona następujące czynności.

foo(int weight, int height, int age = 0, string gender = null);

Teraz refaktoryzacja nie jest wymagana, a funkcja może zostać uruchomiona w trybie starszego typu, gdy genderjest pusta.

Aby określić konkretną genderwartość, jest teraz TWARDY w wezwaniu do age. Jako przykład:

 foo(10,10,0,"female");

Programista spojrzał na definicję funkcji, zobaczył, że agema wartość domyślną 0i użył tej wartości.

Teraz domyślna wartość agew definicji funkcji jest całkowicie bezużyteczna.

Nazwane parametry pozwalają uniknąć tego problemu.

foo(weight: 10, height: 10, gender: "female");

Można dodawać nowe parametry fooi nie musisz się martwić kolejnością, w jakiej zostały dodane, i możesz zmienić wartości domyślne wiedząc, że ustawiona wartość domyślna to tak naprawdę prawdziwa wartość domyślna.

Reactgular
źródło