Ktokolwiek, kto programował dłużej w Windows API zna bardzo dobrze klasyczną sekwencję instrukcji, składającą się na zarejestrowanie nowej klasy okna i jego utworzenie. Jej częścią jest między innymi wywołanie funkcji CreateWindow
lub CreateWindowEx
, które przyjmują odpowiednio 11 lub 12 parametrów. Mimo że nie są one rekordzistkami pod tym względem (bije je chociażby CreateFont
z 14 argumentami), to i tak mogą się “poszczycić” dużym potencjałem w zaciemniania kodu i czynienia go trudnym w zrozumieniu lub modyfikacji.
Niestety, takie lub nieco mniej drastyczne przypadki można spotkać w wielu językach, platformach i technologiach. Odchodzą one daleko od rozsądnego zalecenia, aby liczba parametrów funkcji nie przekraczała dwóch lub trzech, z ewentualnym uwzględnieniem this
/self
/Me
. Jak sobie z nimi radzić, aby wynikowy kod zawierający tak rozrośnięte wywołania był jeszcze w jakikolwiek sposób czytelny?…
Otóż należy postarać się, aby każdy z wielu argumentów był identyfikowalny czymś więcej niż tylko pozycją w ciągu oddzielonym przecinkami. Dobrze tutaj sprawdza się feature niektórych języków programowania zwany argumentami słownikowymi. Umożliwia on “przypisywanie” w wywołaniu wartości parametrów do ich nazw. Pozwala to na zmianę ich kolejności, ale przede wszystkim dodaje czytelną etykietę dla każdego argumentu. Przykład takiego słownikowego wywołania w Pythonie widać poniżej:
Teoretycznie podobny efekt można osiągnąć także w językach nieposiadających wspomnianej opcji. Podejrzewam zresztą, że sposób ten jest pierwszym, jaki większości przyszedł do głowy. Chodzi tu o zwyczajne opatrzenie każdego argumentu odpowiednim komentarzem. Wiele przykładów tak właśnie traktuje argumenty wspomnianej funkcji CreateWindow
(Ex
):
Ale rzeczywisty kod to nie przykład z tutoriala, a nadmiar kolorowych komentarzy niekoniecznie musi dobrze wpływać na przejrzystość całej instrukcji. W dodatku wciąż jesteśmy skazani na domyślną kolejność parametrów, a wszelkie rozbieżności między argumentami a ich opisem (bardzo mylące!) nie są wykrywane przez kompilator…
Co można zatem zrobić? Odpowiedź jest prosta: należy napisać kod, który sam się dokumentuje ;-) A rozwijając tę myśl do czegoś bardziej konkretnego: powinniśmy zauważyć, że absolutnie każdy język posiada możliwość opisywania nie tylko parametrów funkcji, ale ogóle jakichkolwiek wyrażeń. Nazywa się to… dokładnie tak – deklaracją zmiennych:
Przy takim rozwiązaniu niepotrzebne są już żadne dodatkowe wyjaśnienia, bo wszystko widać tu doskonale. Wywołanie stało się czytelne, bo każdy z parametrów jest po prostu swoją nazwą lub nieistotnym NULL
-em. Warto też zauważyć, że w typowym kodzie wiele z tych nazw byłoby już zdefiniowanych wcześniej, bo np. byłyby argumentami funkcji otaczającej to wszystko. Ilość dodatkowych deklaracji niekoniecznie musiałaby więc być zbliżona do długości listy parametrów wywołania.
Powyżej widać zatem, że nawet z wyjątkowo rozrośniętymi funkcjami można sobie całkiem nieźle poradzić. Nie traktujmy tego jednak jako zachęty do wydłużania list argumentów naszych własnych funkcji. Zdecydowanie lepiej jest użyć struktury (jak to robi się np. przy tworzeniu urządzenia DirevtX) czy nawet wzorca Builder bez jego abstrakcyjnej części.
Byś popisał trochę w Objective C, to by ci przeszła ochota na opisowe wywołania funkcji :)
NSString * str = [NSString stringWithString:@"Mój napis"];
NSString * str2 = [str stringByReplacingOccurrencesOfString:@"napis" withString:@"inny napis"];
Albo jesteśmy bardzo (ale to bardzo) zdeterminowani żeby kod był najczytelniejszy z możliwych to napisać wrappera na funkcję (z jakimś ‘fluent interfacem’)
key = lambda s: len(s)? Huh… Rozumiem, że lambdy są fajne, ale key=len jest równie funkcyjne i dużo ładniejsze. :3
a jeśli będę zbyt dużo kodu pisał w ten sposób, nie grozi mi przez to zapchanie pamięci zmiennymi, które są jednym słowem – śmieciowe?
nie wszędzie jest sens używać garbage collectora bo przy małych programach na których liczy się wydajność i mają mało pamięci to by się przydał jako taki porządek…
@marekk: To są zmienne lokalne, alokowane na stosie więc garbage collecting niespecjalnie ich dotyczy. Mogą one stać się problemem dopiero przy bardzo małym rozmiarze stosu i/lub bardzo wielu poziomach rekurencji.Ten pierwszy przypadek zdarza się pewnie tylko na jakichś ‘bardzo embedded’ platformach, a ten drugi to najpewniej kiepski algorytm :) Co do wydajności, to sama alokacja na stosie trwa tyle co nic, tj. przesunięcie jednego wskaźnika w rejestrze.
Oczywiście to wszystko jest pod warunkiem, że zmienne te nie zostaną wyoptymalizowane przez kompilator.
@Kos: Tak, lambdy są fajne ;-) A na serio to po prostu absurdalność tego zapisu jakoś nie rzuciła mi się w oczy.
Problem jest raczej bardziej ogólny i nie dotoczy tylko wywoływania funkcji, a wszystkich fragmentów kodu w których pojawiają się same liczby – magiczne liczby – bo po pewnym czasie nikt nie wie skąd się one wzięły i co one oznaczają :D.