Lekkostrawnie o definiowaniu funkcji, czyli jak pisać funkcje żeby dało się ich używać

Lekkostrawnie o definiowaniu funkcji, czyli jak pisać funkcje żeby dało się ich używać

W poprzedniej części tego artykułu dowiedziałeś się że długi i skomplikowany kod trzeba dzielić na mniejsze „reużywalne” fragmenty zwane funkcjami. Wiesz też, że funkcje mogą przyjmować parametry, mogą je zmieniać i mogą zwracać wynik.

W tej części dowiesz się czym jest definicja funkcji, po co się ją robi i jak powinno się ją robić.

Zanim zaczniesz pisanie instrukcji które funkcja ma wykonywać, musisz wskazać że dany zestaw poleceń będzie funkcją – musisz napisać definicję funkcji.

Definicja funkcji jest jak recepta od lekarza

Lekarz wypisuje receptę żeby farmaceuta w aptece wiedział jakie leki ma Ci wydać, w jakiej ilości i w jakiej dawce. Lekarz zapisuje Ci także jak te leki przyjmować żeby zadziałały tak jak powinny.

Domyślasz się pewnie co się dzieje jeśli recepta lub instrukcje dawkowania są nieczytelne?

Podczas definiowania funkcji Ty przekazujesz innemu programiście (i maszynie) informacje niezbędne do tego by można było tej funkcji użyć.

Informacjami bez których prawidłowe użycie funkcji nie jest możliwe są:

  • nazwa (choć czasem nie jest ona wymagana, o czym przeczytasz dalej),
  • parametry które trzeba jej podać, lub informacja że żadne nie są wymagane,
  • ewentualne wartości domyślne parametrów (jeśli są jakiekolwiek),
  • instrukcje które maszyna ma wykonać po uruchomieniu.

W językach statycznie typowanych (czyli takich w których zmienne muszą mieć narzucony typ przechowanych wartości) definicja funkcji musi także zawierać typ zwracanego wyniku (jeśli funkcja w ogóle jakiś wynik ma zwracać).

W większości języków programowania definicja funkcji będzie wyglądać mniej więcej tak:

function nazwaFunkcji(parametr1, parametr2 = wartośćDomyślna){
  pierwszaInstrukcjaDoWykonania;
  drugaInstrukcjaDoWykonania;
  return zwracanaWartość;
}

lub

nazwaFunkcji = function(parametr1, parametr2 = wartośćDomyślna){
  instrukcjeDoWykonania;
  return zwracanaWartość;
}

na przykład:

function wykonajPrzelew(kwota, nrKontaBankowegoPlatnika, nrKontaBankowegoSprzedawcy){
  stanKonta[nrKontaBankowegoSprzedawcy] = stanKonta[nrKontaBankowegoSprzedawcy] + kwota;
  stanKonta[nrKontaBankowegoPlatnika] = stanKonta[nrKontaBankowegoPlatnika] - kwota;
  return stanKonta[nrKontaBankowegoPlatnika];
}

Definicja funkcji powinna być tak napisana, żeby było w 100% jasne co ona zrobi dla danych które jej przekażemy i czego można się po niej spodziewać. To że wiesz po co ją piszesz teraz, nie oznacza że będziesz to wiedział za rok kiedy przyjdzie Ci do tego kodu wrócić.

Przeczytałem niedawno ciekawe określenie jak powinno się pisać kod (nie tylko funkcji):

…programuj tak jakbyś to robił dla osoby, która będzie później utrzymywać ten kod. “Rób to tak, jakby to był brutalny psychopata, który wie gdzie mieszkasz”…

(źródło: https://devcave.pl/notatnik-juniora/zasady-projektowania-kodu)

Nic dodać nic ująć.

Żeby ten brutalny psychopata umiał skorzystać z Twojej funkcji, pierwsze co musisz zrobić, to właściwie ją nazwać.

Nazwa funkcji jest jak oznaczenie na przyciskach na pilocie do telewizora

Konkretne, intuicyjne i w miarę możliwości krótkie sprawiają, że korzysta się z nich wygodnie i bezpiecznie.

Nazwa funkcji powinna określać co funkcja robi (na przykład zapiszArtykul lub storeArticle), lub co zwraca (np. zwrocArtykul lub getArticle).

Jeśli odpowiednio nazwiesz funkcję, wówczas już po samej nazwie widać co ona zrobi, a czego na pewno nie zrobi.

Możesz oczywiście nazwać swoje funkcje funkcja1 i funkcja2, ale gwarantuję Ci, że po jakimś czasie zapomnisz co się w nich dzieje i będziesz musiał poświęcić dużo czasu i solidnie wgryźć się kod żeby odświeżyć sobie pamięć.

Polecam także stosowanie nazewnictwa angielskiego zamiast polskiego. Po pierwsze dlatego, że nazwy mogą być wtedy prostsze i krótsze. A po drugie: kod pisany „po angielsku” będzie zrozumiany nie tylko nad Wisłą, ale także poza granicami naszego kraju. Lepiej od razu utrwalać sobie dobre nawyki, bo nie wiadomo w jakim zespole przyjdzie Ci pracować w przyszłości.

Jeśli jednak wygodniej Ci kodować po polsku, sugeruję nie stosować liter „z ogonkami”. Niektóre języki programowania umożliwiają stosowanie polskich liter w nazwach zmiennych i funkcji, ale nie polecamy tego z uwagi na różne metody kodowania znaków narodowych w edytorach kodu i problemy z refaktoringiem (automatycznym przeorganizowaniem kodu).

Nazwa funkcji pozwala na oznaczenie zestawu instrukcji w ten sposób, by za jej pomocą dało się kod uruchomić zawsze wtedy gdy będzie to konieczne.

W dwóch przypadkach jednak nazywanie funkcji nie ma sensu:

  • gdy instrukcje muszą być swego rodzaju dodatkiem do istniejącej funkcji i zostać „wstrzyknięte” do niej, lub
  • gdy dany zestaw instrukcji musi być wykonany od razu i tylko raz, na przykład jeśli chcemy odizolować jakiś inicjujący fragment programu od reszty.

Takie funkcje nazywamy anonimowymi (lub literałami funkcyjnymi lub funkcjami lambda). Temat ten jednak poruszymy w bardziej zaawansowanym artykule.

 

Sensownie nazwij też parametry funkcji

Jeśli funkcja wymaga podania parametrów, konieczne są informacje:

  • o tym jakiego mają być typu (np. liczba czy tablica liczb),
  • w jakich jednostkach mają one być podane (np. litry czy metry sześcienne).
function obliczCzasPrzejazdu(predkosc, odleglosc){}

Domyślasz się pewnie, że prędkość powinna być podana w km/h a odległość w kilometrach, ale co jeśli brutalny psychopata będzie używać mil?

W takiej sytuacji masz do wyboru 2 opcje:

  • nazwać parametry tak żeby zawrzeć w nich jednostki np. predkosc_KmNaH, lub
  • dopisać odpowiedni komentarz do funkcji i w nim opisać szczegóły dotyczące każdego z parametrów.
/*zwraca czas przejazdu [h]
predkosc - prędkość pojazdu [km/h]
odleglosc - odległość do przejechania [km]
*/

function obliczCzasPrzejazdu(predkosc, odleglosc){}
/*wysyła maila pod wskazany adres
adresEmail - adres email odbiorcy lub tablica adresów
tytul - tytul maila (kodowany w UTF8)
treść - tytul maila (kodowany w UTF8)
*/
function wyslijMaila(adresEmail, tytul, tresc){}

Przeciążanie funkcji

W niektórych językach programowania możliwe jest zdefiniowanie obok siebie kilku funkcji o tej samej nazwie. Wygodne jest to na przykład wtedy, gdy to samo zadanie może zostać wykonane z wykorzystaniem różnych danych.

Wyobraź sobie automat z kawą w którym jest możliwość zapłaty gotówką, kartą płatniczą lub kodem blik.

Funkcja przyjmijPlatnosc jego oprogramowania mogłaby wtedy przyjmować trzy zestawy parametrów:

  • kwota (dla płatności gotówką),
  • kwota, numer karty, pin do karty (dla płatności kartą), lub
  • kwota, kod blik (do płatności blikiem).

Jeśli język w którym napisane jest oprogramowanie automatu umożliwia przeciążanie funkcji, wówczas wygodne byłoby napisanie trzech niezależnych funkcji:

function przyjmijPlatnosc(kwota){...}
function przyjmijPlatnosc(kwota, numerKarty, pinDoKarty){..}
function przyjmijPlatnosc(kwota, kodBlik){...}

Nic nie stoi na przeszkodzie żeby każdą z tych funkcji nazwać inaczej, ale zauważ że brak konieczności „odmieniania” nazwy funkcji przez przypadki znacznie upraszcza korzystanie z niej (nich).

To tyle w temacie definiowania funkcji. W kolejnym dowiesz się kilku ciekawych rzeczy na temat funkcji rekurencyjnych, które czasem mocno się przydają, ale czasem też dają programistom mocno w kość.

Masz pytania – pytaj w komentarzu.

Dodaj komentarz