Lekkostrawnie o funkcjach, czyli 6 rzeczy o których musisz wiedzieć zanim napiszesz swój pierwszy kawałek kodu

Lekkostrawnie o funkcjach, czyli 6 rzeczy o których musisz wiedzieć zanim napiszesz swój pierwszy kawałek kodu

Funkcje występują w każdym nowoczesnym języku programowania. Pisanie aplikacji bez znajomości tego zagadnienia jest jak łowienie ryb bez umiejętności rozłożenia wędki. Da się, ale efekty mogą być mizerne.
Poznaj wszystko co ważne na temat funkcji, by łowić ryby wielkie.

Kiedy kod jest długi i nieczytelny…

Często zdarza się, że pisząc kod aplikacji rozrasta on się mam na tyle, że trzeba go długo przewijać zanim znajdzie się odpowiedni fragment. Instrukcje takiego programu są nieczytelne, nie mówiąc już o ich zrozumieniu.

Wyobraź sobie, że piszesz aplikację rozwiązującą dość popularny (mam nadzieję) temat sprzątania mieszkania.
Nie wiem jak u z Ciebie, ale w moim przypadku sprzątanie mieszkania to następujący szereg 500 zadań do wykonania:

– zabrać rzeczy z półek
– powycierać kurze
– odstawić krzesła
– odkurzyć dywany
– zwinąć dywany
– odkurzyć podłogę
– umyć podłogę
– rozwinąć dywany
– przystawić krzesła
– pozmywać talerze
– pozmywać sztućce
– pozmywać szklanki
– opłukać talerze
– opłukać sztućce
– opłukać szklanki
– odłożyć talerze na ociekacz
– odłożyć sztućce do koszyka
– odłożyć szklanki na ociekacz
– rozdzielić ubrania na białe i kolorowe
– włożyć do pralki ubrania białe
– wsypać proszek do białego
– uprać ubrania białe
– powiesić ubrania białe do wysuszenia
– wyprasować ubrania białe
– odłożyć ubrania białe do szafy
– włożyć do pralki ubrania kolorowe
– wsypać proszek do kolorowych
– uprać ubrania kolorowe
– powiesić ubrania kolorowe do wysuszenia
– wyprasować ubrania kolorowe
– odłożyć ubrania kolorowe do szafy
– umyć wannę
– umyć umywalki
– umyć kibel
– wynieść śmieci
– włożyć nowy worek do kosza

Wiedziałem że nie przeczytasz wszystkiego.
Wyobraź sobie że piszesz oprogramowanie robota, który realizuje te wszystkie zadania, uruchamiając następujące instrukcje:

zabierzRzeczyZPółek;
wytrzyjKurze;
odstawKrzesła;
odkurzDywany;
zwińDywany;
odkurzPodłogę;
umyjPodłogę;
rozwińDywany;
przystawKrzesła;
pozmywajTalerze;
pozmywajSztućce;
pozmywajSzklanki;
opłuczTalerze;
opłuczSztućce;
opłuczSzklanki;
odłóżTalerzeNaOciekacz;
odłóżSztućceDoKoszyka;
odłóżSzklankiNaOciekacz;
rozdzielUbraniaNaBiałeIKolorowe;
włóżDoPralkiUbraniaBiałe;
wsypProszekDoBiałego;
upierzUbraniaBiałe;
powieśUbraniaBiałeDoWysuszenia;
wyprasujUbraniaBiałe;
odłóżUbraniaBiałeDoSzafy;
włóżDoPralkiUbraniaKolorowe;
wsypProszekDoKolorów;
upierzUbraniaKolorowe;
powieśUbraniaKoloroweDoWysuszenia;
wyprasujUbraniaKolorowe;
odłóżUbraniaKoloroweDoSzafy;
umyjWannę;
umyjUmywalki;
umyjKibel;
wynieśŚmieci;
włóżNowyWorekDoKosza;

Tym razem też na pewno nie przeczytałeś wszystkich instrukcji. Podobnie byłoby gdybyś dostał do analizy w ten sposób napisany kod dowolnego innego programu. Przebrnięcie przez całość takiego tworu, nie mówiąc już o zrozumieniu i znalezieniu błędów, było by koszmarem.
Powstaje zatem pytanie…

Jak skomplikowany kod napisać dobrze?

Jakkolwiek byśmy nie podeszli do tematu, to niestety wszystkie operacje wypisane w poprzedniej części muszą zostać wykonane.
Aby kod uprościć, trzeba jego instrukcje pogrupować. A robi się to właśnie za pomocą funkcji.

Funkcja to grupa instrukcji zamkniętych w jedną instrukcję.

Dzięki zastosowaniu funkcji możemy podzielić instrukcje programu na kilka większych zadań i wykonać je po kolei.


Tak więc powyższe 500 instrukcji można zamienić na następujące „zadania”:

wytrzyjKurze;
posprzątajPodłogi;
pozmywajNaczynia;
zróbPranie;
umyjŁazienki;
wynieśŚmieci;

Każde z tych zadań składać się będzie ze swoich zadań cząstkowych.
Dla przykładu funkcja posprzątajPodłogi będzie wyglądała mniej więcej tak:

function posprzątajPodłogi(){
 odstawKrzesła;
 odkurzDywany;
 zwińDywany;
 odkurzPodłogę;
 umyjPodłogę;
 rozwińDywany;
 przystawKrzesła;
}

Organizując kod w ten sposób i uruchamiając zadania po kolei (zamiast osobno każdej z instrukcji cząstkowych) dużo łatwiej jest ogarnąć co się w nim dzieje.

Uwaga brzydkie słowo: reużywalność

Jeśli uważnie czytałeś wcześniejszą treść tego artykułu, to pewnie zauważyłeś, że w kodzie który napisałem wyżej, znalazły się zestawy instrukcji bardzo zbliżonych do siebie:

włóżDoPralkiUbraniaBiałe;
wsypProszekDoBiałego;
upierzUbraniaBiałe;
powieśUbraniaBiałeDoWysuszenia;
wyprasujUbraniaBiałe;
odłóżUbraniaBiałeDoSzafy;

włóżDoPralkiUbraniaKolorowe;
wsypProszekDoKolorów;
upierzUbraniaKolorowe;
powieśUbraniaKoloroweDoWysuszenia;
wyprasujUbraniaKolorowe;
odłóżUbraniaKoloroweDoSzafy;

Nikt oczywiście nie zabroni Ci napisać programu „powielając” w ten sposób linie kodu, ale jeśli musiałbyś coś w nim poprawić, musiałbyś to zrobić i dla prania białego i kolorowego.
Jeśli chciałbyś dodatkowo uprać skarpetki nie mieszając ich ani z białym ani z kolorowym, to kolejny raz musiałbyś użyć KOPIUJ – WKLEJ i stworzyć trzecią odmianę tego samego kodu.

Można to zrobić lepiej pisząc ogólną funkcję odświeżUbrania, która robiła by to samo, ale z ubraniami i proszkiem który jej wskażesz.

function odświeżUbrania(ubrania, proszek){
 włóżDoPralki(ubrania);
 wsypProszek(proszek);
 upierz(ubrania);
 powieśDoWysuszenia(ubrania);
 wyprasuj(ubrania);
 odłóżDoSzafy(ubrania);
}

Dzięki temu masz jedną zgrabną funkcję którą tylko musisz wywołać 2 razy:

odświeżUbrania(ubraniaBiale, proszekDoBialego);
odświeżUbrania(ubraniaKolorowe, proszekDoKolorów);

W każdej chwili możesz też zaszaleć i wywołać…

odświeżUbrania(skarpetki, proszekDoKolorów);

Ekstra, nie?

Musisz tylko pamiętać żeby nie wywołać funkcji niewłaściwie:

odświeżUbrania(ubraniaBiale, proszekDoKolorów);

Funkcje mogą przyjmować argumenty

W przykładzie powyżej przekazaliśmy funkcji odświeżUbrania dodatkowe informacje które były jej potrzebne do pracy. Zrobiliśmy to właśnie za pomocą argumentów. W naszym przypadku funkcja wymaga ich, żeby mogła działać poprawnie.

W niektórych przypadkach może się okazać, że do funkcji chcielibyśmy przekazać jeszcze jakieś dodatkowe informacje sterujące. Na przykład moglibyśmy chcieć w pewnych sytuacjach, by nasza funkcja odświeżUbrania zadziałała trochę inaczej i pomijała prasowanie, a dodatkowo dała możliwość decydowania gdzie mają być składowane uprane ciuchy. Dopisali byśmy wtedy dwa dodatkowe argumenty:

  • czyPrasować, by za jego pomocą decydować, czy brać się za tą niezwykle przyjemną czynność, czy nie,
  • gdzieOdłożyć, by wskazywać miejsce docelowe.
function odświeżUbrania(ubrania, proszek, czyPrasowac, gdzieOdłożyć){
  włóżDoPralki(ubrania);
  wsypProszek(proszek);
  upierz(ubrania);
  powieśDoWysuszenia(ubrania);
  if (czyPrasowac)
    wyprasuj(ubrania);
  odłóż(ubrania, gdzieOdłożyć);
}

Teraz tylko wystarczy uruchomić nasze zadania:

odświeżUbrania(ubraniaBiale, proszekDoBialego, TRUE, szafa);
odświeżUbrania(ubraniaKolorowe, proszekDoKolorów, TRUE, szafa);
odświeżUbrania(skarpetki, proszekDoKolorów, FALSE, szuflada);

Można też trochę ułatwić sobie uruchamianie funkcji z dużą liczbą argumentów, określając części z nich wartości domyślne:

function odświeżUbrania(ubrania, proszek, czyPrasowac = TRUE, gdzieOdłożyć = szafa){
   ...
}

Wówczas te dodatkowe dane będziemy musieli podać tylko dla nietypowych przypadków:

odświeżUbrania(ubraniaBiale, proszekDoBialego);
odświeżUbraniaubraniaKolorowe, proszekDoKolorów);
odświeżUbrania(skarpetki, proszekDoKolorów, FALSE, szuflada);

Uwaga!
W większości języków programowania, jeśli jakiś argument będzie miał ustawioną wartość domyślną, to wszystkie argumenty z jego prawej strony też muszą jakąś domyślną wartość posiadać.

Funkcje mogą zmieniać wartość swoich argumentów

Zwróć uwagę jak działa zmywarka: wkładasz do niej naczynia, wsypujesz proszek lub wkładasz tabletkę i maszyneria rusza.

umyjNaczynia(naczynia, proszek);

W typ przypadku funkcja umyjNaczynia przyjmuje 2 argumenty: naczynia które trzeba umyć i proszek, który trzeba rozpuścić w wodzie żeby naczynia się umyły, a nie tylko zmoczyły.
Po wykonaniu funkcji umyjNaczynia argument, który podałeś wywołując ją, został zmieniony. Już nie są to brudne gary, tylko czyste, świecące i pachnące garnuszki.

Funkcje mogą przyjmować argumenty i tylko odczytywać ich wartość, ale mogą też tą wartość zmieniać.
Jest to o tyle ważne, że pisząc funkcję musisz wiedzieć czy możesz zmienić wartość argumentu, czy nie wolno Ci tego zrobić.

Wyobraź sobie odtwarzacz DVD, który ma wbudowaną funkcję

function odtwórzFilm(płytaDVD){
   wczorajDaneZPłyty;
   wyświetlFilm;
}

Funkcja ta musi tak działać, żeby płyta włożona do napędu nie została skasowana, uszkodzona, ani nadpisana innym filmem.
Uruchamiając film na odtwarzaczu, lub uruchamiając funkcję odtwórzFilm musisz mieć pewność że płyta / argument funkcji zostanie nienaruszona.
Odtwarzacz może mieć też funkcję nagrajPłytę(płytaDVD), która z założenia płytę / argument ma prawo zmienić.

function nagrajPłytę(płytaDVD){
  wczytajDaneDoNagrania;
  nagrajDaneNaPłycie;
  testujPłytę;
}

Funkcje mogą zwracać wynik

W poprzednich przykładach mieliśmy do czynienia z funkcjami które po prostu wykonywały pewne instrukcje. Często się jednak zdarza, że uruchamiając funkcję chcielibyśmy uzyskać jakiś wynik jej działania.

Jeśli funkcja ma zwracać wynik, w jej kodzie musi być wskazana wartość która ma stać się tym wynikiem. Najczęściej robi się to za pomocą słowa return.

function nazwaFunkcji(argument1, argument2,...){
  instrukcja1;
  instrukcja2;
  ....
  return wynik;
}

Pozostając w temacie AGD wyobraź sobie kapsułkowy ekspres do kawy uruchamiający funkcję zróbKawę przyjmującą argumenty kapsułkaZKawą i kapsułkaZMlekiem.
Wynikiem działania takiej funkcji jest kawa:

function zróbKawę(kapsułkaZKawą, kapsułkaZMlekiem){
  zalogujWodę;
  przelejWodęPrzez(kapsułkaZMlekiem);
  przelejWodęPrzez(kapsułkaZKawą);
  return kawa;
}

Jeśli zakładasz że funkcja ma zwracać rezultat, to pisząc jej treść staraj się pamiętać o tym, żeby dla każdej sytuacji jakiś wynik został zwrócony. W większości języków programowania jeśli nie wskażesz wyniku działania funkcji zwróci ona wartość pustą (nil lub null), ale może się też okazać że dostaniesz wartość losową, a wtedy trudne jest stwierdzenie czy funkcja zadziałała prawidłowo czy nie.

Jeśli uruchomisz funkcję zróbKawę, a w ekspresie zepsuje się grzałka, wówczas lepiej jest dostać pustą filiżankę, niż kawę zalaną zimną wodą.

Wiesz już zatem jak grupować instrukcje w funkcje, po co się to robi i jak skompresować kod, by czytało się go łatwiej, łatwiej poprawiało i można było go używać wielokrotnie.
Masz pytania? Coś jest niejasne? Daj znać w komentarzu

Nie wyczerpałem oczywiście tematu funkcji, więc planuję kolejne części tego artykułu.

Przeczytasz w nich między innymi:

  • o czarnych skrzynkach,
  • o definiowaniu funkcji i uruchamiania ich,
  • o przeciążaniu funkcji,
  • o tym, że funkcje czasem są niecierpliwe i nie czekają aż poprzednia funkcja skończy działanie,
  • o tym, że funkcja może uruchomić samą siebie, co jest jednocześnie piękne i niebezpieczne.

Nie chcesz przegapić kolejnej części? Polub i najlepiej skomentuj tego posta na Facebooku pisząc co myślisz o tym artykule.
No i polub stronę Kodowisko na Facebooku jeśli uważasz że warto rozwijać ten projekt.

2 komentarze do wpisu „Lekkostrawnie o funkcjach, czyli 6 rzeczy o których musisz wiedzieć zanim napiszesz swój pierwszy kawałek kodu

  1. Przeczytałem i muszę powiedzieć, że nie za bardzo trafiają do mnie te przykłady z „sprzątaniem”. Czegoś mi brakuje w tym artykule. Wiele z tego nie zrozumiałem. Potencjał jest, ale wydaje mi się, że mogło to zostać wytłumaczone o wiele lepiej.

    • Może przykład ze sprzątaniem jest trochę przekombinowany, ale oddaje sens upraszczania kodu i dzielenia go na kawałki.
      Lepiej podzielić skomplikowany problem na kilka mniejszych i rozwiązywać je po kolei.
      Jeśli coś jest niezrozumiałe – pytaj. Spróbuję pomóc

Pozostaw odpowiedź Dawid Anuluj pisanie odpowiedzi