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?
Odpowiedzi:
Podanie nazw parametrów nie zawsze sprawia, że kod jest bardziej czytelny: na przykład, czy wolałbyś czytać
min(first=0, second=1)
czymin(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:
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 .
źródło
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” - chceColor(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
DateTime
interfejsu API, który miał dwie klasy rodzeństwa dla punktów i czasów trwania z prawie identycznymi interfejsami. ChociażDateTime->new(...)
zaakceptowałsecond => 30
argument,DateTime::Duration->new(...)
poszukiwanyseconds => 30
i 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:
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.
pch
jest w rzeczywistości znakiem wykresu, glifem, który zostanie narysowany dla każdego punktu danych.17
jest pustym okręgiem lub coś takiego.lty
jest typem linii. Oto1
linia ciągła.bty
jest 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:
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:
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:
źródło
it is easier to forget the exact names of some parameters than it is to forget their order
” ... to interesujące stwierdzenie.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ść.
źródło
sin(radians=2)
, nie potrzebujesz „Intellisense”.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.
źródło
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.
źródło
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 podpisfoo()
zmian, alefoo(name:1)
jest mniej prawdopodobne, że pęknie, wymagając od programisty mniejszego wysiłku, aby wprowadzić zmianyfoo
.Co robisz, gdy musisz wprowadzić nowy parametr do następującej funkcji, a ta funkcja zawiera setki lub tysiące wierszy kodu?
Większość programistów wykona następujące czynności.
Teraz refaktoryzacja nie jest wymagana, a funkcja może zostać uruchomiona w trybie starszego typu, gdy
gender
jest pusta.Aby określić konkretną
gender
wartość, jest teraz TWARDY w wezwaniu doage
. Jako przykład:Programista spojrzał na definicję funkcji, zobaczył, że
age
ma wartość domyślną0
i użył tej wartości.Teraz domyślna wartość
age
w definicji funkcji jest całkowicie bezużyteczna.Nazwane parametry pozwalają uniknąć tego problemu.
Można dodawać nowe parametry
foo
i 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.źródło