Uwaga! Trwają prace nad nową wersją serwisu. Mogą występować przejściowe problemy z jego funkcjonowaniem. Przepraszam za niedogodności!
⛔ Potrzebujesz wsparcia? Oceny CV? A może Code Review? ✅ Dołącz do Discorda!
Jeżeli jesteś na początku nauki programowania, być może piszesz kod linijka po linijce i jeszcze trudno Ci rozpoznać, w jaki sposób dzielić kod na mniejsze fragmenty. Zobacz, jak rozbić długie funkcje na krótsze i prawidłowo przekazywać argumenty. Dzięki temu Twój kod będzie bardziej czytelny, reużywalny i elastyczny.
Żeby podzielić długą funkcję na mniejsze, najpierw musimy ją mieć. Poniżej znajdziesz kod HTML i JavaScript gry losującej szczęśliwy numer. Jej działanie jest bardzo proste:
<!DOCTYPE html> <html lang="pl"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" href="style.css" /> <title>Jak dzielić funkcje JavaScript na mniejsze | devmentor.pl</title> </head> <body> <main class="game"> <h1 class="luckyHeader">Szczęśliwa liczba to: <span class="luckyNumber"></span></h1> <button class="luckyButton">Losuj!</button> </main> <script src="app.js"></script> </body> </html>
// Uruchamiam kod JS dopiero po załadowaniu drzewa DOM document.addEventListener('DOMContentLoaded', function () { let roundsPlayed = 1; // Wyszukuję w DOM element dla umieszczenia szczęśliwej liczby const luckyNumberEl = document.querySelector('.luckyNumber'); // Generuję szczęśliwą liczbę i wyświetlam ją użytkownikowi const luckyNumber = Math.floor(Math.random() * 5) + 1; luckyNumberEl.innerText = luckyNumber; // Wyszukuję przycisk losowania i dodaję do niego nasłuchiwanie na kliknięcie // Po kliknięciu uruchomi się funkcja playRound() const luckyButton = document.querySelector('.luckyButton'); luckyButton.addEventListener('click', playRound); function playRound() { // Losuję liczbę dla użytkownika const userNumber = Math.floor(Math.random() * 5) + 1; const gameEl = document.querySelector('.game'); // Tworzę element wiadomości o wygranej/przegranej i dodaję go do elementu main const messageEl = document.createElement('p'); messageEl.classList.add('message'); gameEl.appendChild(messageEl); if (luckyNumber === userNumber) { // Jeśli użytkownik wygrywa, wyświetlam odpowiednią wiadomość messageEl.innerText = `Gratulacje, wygrałeś! Twoja szczęśliwa liczba to: ${userNumber}. Koniec gry.`; messageEl.style.color = 'red'; // Blokuję przycisk losowania i nadaję mu odpowiedni styl dzięki klasie luckyButton.disabled = true; luckyButton.classList.add('button-disabled'); } else { // Jeśli użytkownik przegrywa i nie jest to ostatnia runda, to otrzymuje adekwatną wiadomość messageEl.innerText = `Niestety, Twoja liczba to: ${userNumber}. Spróbuj jeszcze raz.`; } // Jeśli to ostatnia runda rozgrywki i użytkownik nie wygrał, to... if (roundsPlayed > 2 && luckyNumber !== userNumber) { // Wyświetlam odpowiedni komunikat messageEl.innerText = `Niestety, Twoja liczba to: ${userNumber}. Koniec gry.`; // Blokuję przycisk losowania i nadaję mu odpowiedni styl dzięki klasie luckyButton.disabled = true; luckyButton.classList.add('button-disabled'); } // Zwiększam liczbę rund o 1 roundsPlayed++; } });
Zanim przeniesiemy kod do mniejszych funkcji, najpierw musimy rozpoznać poszczególne jego „grupy”, czyli fragmenty odpowiadające za konkretne funkcjonalności.
Najłatwiej będzie zacząć od kodu, który powielamy. U nas na pierwszy rzut oka jest to:
Zacznijmy więc od tego i zobaczmy, jak „odchudzi się” nasza funkcja playRound.
Na samym dole pliku (poza innymi funkcjami) stwórzmy funkcję do losowania numeru z zakresu 1-5 i wklejmy do niej kod, którego używamy do losowania. Pamiętajmy o słowie return – dzięki niemu funkcja zwróci wartość.
function generateRandomNumber() { return Math.floor(Math.random() * 5) + 1; }
Teraz wywołajmy generateRandomNumber we wszystkich miejscach, w których potrzebujemy losowej liczby.
UWAGA: pamiętaj, by po każdym podstawieniu funkcji w miejsce poprzedniego fragmentu uruchomić kod i sprawdzić, czy działa. Jeśli pojawi się błąd, będziesz mniej więcej wiedzieć, gdzie szukać przyczyny.
Dobrą praktyką jest też nieusuwanie od razu zastępowanych fragmentów kodu, lecz ich zakomentowanie. W razie potrzeby łatwo będzie do nich wrócić.
// Uruchamiam kod JS dopiero po załadowaniu drzewa DOM document.addEventListener('DOMContentLoaded', function () { let roundsPlayed = 1; // Wyszukuję w DOM element dla umieszczenia szczęśliwej liczby const luckyNumberEl = document.querySelector('.luckyNumber'); // Generuję szczęśliwą liczbę i wyświetlam ją użytkownikowi const luckyNumber = generateRandomNumber() luckyNumberEl.innerText = luckyNumber; // Wyszukuję przycisk losowania i dodaję do niego nasłuchiwanie na kliknięcie // Po kliknięciu uruchomi się funkcja playRound() const luckyButton = document.querySelector('.luckyButton'); luckyButton.addEventListener('click', playRound); function playRound() { // Losuję liczbę dla użytkownika const userNumber = generateRandomNumber() const gameEl = document.querySelector('.game'); // ... } }); function generateRandomNumber() { return Math.floor(Math.random() * 5) + 1; }
Podobnie postąpimy z kodem dla blokowania przycisku losowania.
function disableButton() { luckyButton.disabled = true; luckyButton.classList.add('button-disabled'); }
Teraz podstaw funkcję disableButton w miejsce poprzedniego kodu, m.in. tutaj:
if (luckyNumber === userNumber) { // Jeśli użytkownik wygrywa, wyświetlam odpowiednią wiadomość messageEl.innerText = `Gratulacje, wygrałeś! Twoja szczęśliwa liczba to: ${userNumber}.`; messageEl.style.color = 'red'; // Blokuję przycisk losowania i nadaję mu odpowiedni styl dzięki klasie disableButton(); // ... }
Powyższy kod niestety nie zadziała w miejscu wywołania disableButton(). Otrzymamy błąd:
Uncaught ReferenceError: luckyButton is not defined
Dzieje się tak, ponieważ zmienna luckyButton jest zadeklarowana w innym zakresie (dokładniej: w zakresie funkcji anonimowej uruchamianej po załadowaniu drzewa DOM). Funkcje niższego rzędu (jak nasza disableButton) zadeklarowane poza kodem funkcji nadrzędnej nie mogą przez to z tej zmiennej korzystać.
Dlatego dobrą praktyką jest niekorzystanie ze zmiennych z szerszego zakresu (również ze zmiennych globalnych) w obrębie funkcji podrzędnych. Powinny być one „samodzielnymi” fragmentami kodu i korzystać ze zmiennych przekazywanych przez argumenty.
Poza tym w tej chwili funkcja disableButton nie będzie działać dla żadnego innego przycisku (a może nam się to przydać przy rozbudowywaniu gry). Jak to poprawić?
Najpierw określmy sobie ogólną nazwę dla dowolnego przycisku: buttonEl.
function disableButton() { buttonEl.disabled = true; buttonEl.classList.add('button-disabled'); }
Powyższy kod nadal jednak nie będzie działać. Funkcja disableButton nie wie, co oznacza buttonEl, póki w parametrze nie określimy, że ma się go spodziewać i użyć w swoim ciele.
function disableButton(buttonEl) { buttonEl.disabled = true; buttonEl.classList.add('button-disabled'); }
Teraz w każdym miejscu wywołania funkcji disableButton przekażmy jako argument przycisk do zablokowania w taki sposób:
//... // Blokuję przycisk losowania i nadaję mu odpowiedni styl dzięki klasie disableButton(luckyButton); //...
Teraz czas na kod, który nie powiela się w sposób „oczywisty” (bo np. korzysta z różnych elementów DOM) lub nie powiela się wcale.
Znów pomoże nam grupowanie kodu, czyli zrozumienie, za co odpowiedzialne są poszczególne bloki. Jeśli masz trudność z namierzeniem fragmentów, które można przenieść do osobnych funkcji, możesz opisać sobie komentarzami, jaka jest ich rola. To będzie wskazówka, co umieścić w mniejszej funkcji.
Zacznijmy od góry naszego pliku. Pierwszy „pakiet kodu”, który rzuca mi się w oczy, to generowanie wiadomości o wygranej/przegranej i dodawanie jej do elementu main.
// Tworzę element wiadomości o wygranej/przegranej i dodaję go do elementu main const messageEl = document.createElement('p'); messageEl.classList.add('message'); gameEl.appendChild(messageEl);
Tak wygląda nowa funkcja:
function createMessageElement(container) { const messageEl = document.createElement('p'); messageEl.classList.add('message'); container.appendChild(messageEl); return messageEl; }
Zwróć uwagę, że na końcu zwracamy messageEl oraz że przekazujemy przez parametr kontener (rodzica), do którego ma trafić element wiadomości. Jeżeli nie przekazalibyśmy tej wartości, to po wywołaniu funkcji otrzymalibyśmy błąd (możesz to sprawdzić u siebie):
Uncaught TypeError: Cannot read properties of undefined (reading 'appendChild')
To 'appendChild' byłoby dla nas świetną wskazówką – mówiłoby nam, że próbujemy dodać dziecko do nieistniejącego elementu. Warto ze zrozumieniem czytać komunikaty błędów, bo dzięki temu szybciej znajdziemy ich przyczynę.
Zapiszmy teraz wynik działania funkcji do dotychczasowej zmiennej:
// Tworzę element wiadomości o wygranej/przegranej i dodaję go do elementu main const messageEl = createMessageElement(gameEl)
Dzięki temu nasz kod nadal będzie działał, ponieważ korzystamy ze zmiennej messageEl np. przy dodawaniu treści komunikatów, na przykład tutaj:
if (luckyNumber === userNumber) { // Jeśli użytkownik wygrywa, wyświetlam odpowiednią wiadomość messageEl.innerText = `Gratulacje, wygrałeś! Twoja szczęśliwa liczba to: ${userNumber}. Koniec gry.`; messageEl.style.color = 'red'; // ... } // ...
Na końcu zobaczymy, jak jeszcze możemy ulepszyć kod i wykorzystanie tej funkcji.
Czy wiesz, że przenosząc kod do mniejszych funkcji możesz poprawić czytelność instrukcji warunkowej? Pomyślisz: przecież w ten sposób nasz kod się wydłuża. To prawda, ale zyskuje też na czytelności. Interpreter JavaScript bez problemu poradzi sobie z kilkoma dodatkowymi linijkami, a programista przeglądający Twój kod o wiele szybciej się w nim zorientuje!
Spójrzmy na warunek, który sprawdza, czy jest to ostatnia runda rozgrywki i użytkownik nie wygrał. Taki warunek możemy przenieść do osobnej funkcji.
// Jeśli to ostatnia runda rozgrywki i użytkownik nie wygrał, to... if (roundsPlayed > 2 && luckyNumber !== userNumber) { // ... }
Dobierzmy odpowiednią nazwę funkcji. Nazwa zaczynająca się od is będzie wskazywać na to, że spodziewamy się zwrotu wartości typu boolean (true lub false).
Funkcja będzie korzystać z wartości, które znajdują się w innym zakresie, dlatego musimy zapewnić dostęp do nich przez przekazanie jako parametry. Tym razem nazwy parametrów i argumentów zostawiam takie same – nie ma to znaczenia dla wykonania kodu. Gdybyśmy planowali wykorzystać tę funkcję np. w kilku miejscach projektu, wówczas warto byłoby zadbać o bardziej uniwersalne nazwy parametrów.
function isGameLost(roundsPlayed, luckyNumber, userNumber) { return roundsPlayed > 2 && luckyNumber !== userNumber; }
Ponownie pamiętaj o zwróceniu wartości przez return, w przeciwnym razie wartość przekazana do warunku będzie zawsze fałszywa (będzie to undefined). Podmień kod w warunku:
// Jeśli to ostatnia runda rozgrywki i użytkownik nie wygrał, to... if (isGameLost(roundsPlayed, luckyNumber, userNumber)) { // ... }
Wróćmy jeszcze do funkcji createMessageElement. Być może widzisz, że w naszym kodzie powielamy zapis:
messageEl.innerText = treść komunikatu;
Spróbujmy włączyć go do funkcji createMessageElement i przekazywać treść komunikatu przez parametr.
function createMessageElement(container, message) { const messageEl = document.createElement('p'); messageEl.classList.add('message'); messageEl.innerText = message; container.appendChild(messageEl); return messageEl; }
Teraz wprowadzamy zmiany w kilku miejscach (pamiętaj, by po każdej z nich uruchomić kod i sprawdzić, czy działa). Zakomentowujemy (potem usuwamy) niepotrzebne już tworzenie elementu messageEl z początku funkcji playRound i wywołujemy funkcję createMessageElement z odpowiednimi argumentami w miejscach, w których zmienialiśmy innerText paragrafu z komunikatem:
function playRound() { // Losuję liczbę dla użytkownika const userNumber = generateRandomNumber(); const gameEl = document.querySelector('.game'); // Tworzę element wiadomości o wygranej/przegranej i dodaję go do elementu main // const messageEl = createMessageElement(gameEl) if (luckyNumber === userNumber) { // Jeśli użytkownik wygrywa, wyświetlam odpowiednią wiadomość const winMsg = createMessageElement(gameEl, `Gratulacje, wygrałeś! Twoja szczęśliwa liczba to: ${userNumber}. Koniec gry.`); winMsg.style.color = 'red'; // Blokuję przycisk losowania i nadaję mu odpowiedni styl dzięki klasie disableButton(luckyButton); } else { // Jeśli użytkownik przegrywa i nie jest to ostatnia runda, to otrzymuje adekwatną wiadomość createMessageElement(gameEl, `Niestety, Twoja liczba to: ${userNumber}. Spróbuj jeszcze raz.`); } // Jeśli to ostatnia runda rozgrywki i użytkownik nie wygrał, to... if (isGameLost(roundsPlayed, luckyNumber, userNumber)) { // Wyświetlam odpowiedni komunikat createMessageElement(gameEl, `Niestety, Twoja liczba to: ${userNumber}. Koniec gry.`); // Blokuję przycisk losowania i nadaję mu odpowiedni styl dzięki klasie disableButton(luckyButton); } // Zwiększam liczbę rund o 1 roundsPlayed++; }
Uruchom kod i zagraj. Ups! Co się dzieje? Dlaczego na końcu uzyskujemy cztery komunikaty? Gdzieś w naszym kodzie jest błąd. Przeczytasz o tym w następnej sekcji.
Dzięki temu, że nasz kod staje się bardziej czytelny, a za poszczególne zadania zaczynają odpowiadać mniejsze funkcje, możemy wymyślić lepsze rozwiązania lub zoptymalizować aktualne.
W naszym dotychczasowym kodzie nieprawidłowo zaplanowane zostały warunki: pierwszy to blok if-else, a drugi to sam if. Powoduje to, że z końcem przegranej rozgrywki wykonuje się zarówno kod z bloku else, jak i z ostatniego if. Wcześniej było to dla nas nie do wychwycenia, ponieważ zmienialiśmy innerText elementu messageEl (teraz natomiast tworzymy te elementy „od zera” z pomocą funkcji createMessageElement). Działo się to tak szybko, że było niezauważalne dla oka. Dopiero porządki w kodzie ujawniły błąd.
Czytelny kod po refaktoryzacji łatwiej poprawić – nie mamy już bowiem długich fragmentów, pośród których trudno się zorientować.
Teraz, gdy nasz kod po refaktoryzacji jest krótszy, łatwiej wprowadzić poprawki. Okazuje się, że wystarczy włączyć ostatni warunek dotyczący przegranej gry do jednego bloku if-else:
function playRound() { // Losuję liczbę dla użytkownika const userNumber = generateRandomNumber(); const gameEl = document.querySelector('.game'); // Tworzę element wiadomości o wygranej/przegranej i dodaję go do elementu main // const messageEl = createMessageElement(gameEl) if (luckyNumber === userNumber) { // Jeśli użytkownik wygrywa, wyświetlam odpowiednią wiadomość const winMsg = createMessageElement(gameEl, `Gratulacje, wygrałeś! Twoja szczęśliwa liczba to: ${userNumber}. Koniec gry.`); winMsg.style.color = 'red'; // Blokuję przycisk losowania i nadaję mu odpowiedni styl dzięki klasie disableButton(luckyButton); } else if (isGameLost(roundsPlayed, luckyNumber, userNumber)) { // Jeśli to ostatnia runda rozgrywki i użytkownik nie wygrał, to... // Wyświetlam odpowiedni komunikat createMessageElement(gameEl, `Niestety, Twoja liczba to: ${userNumber}. Koniec gry.`); // Blokuję przycisk losowania i nadaję mu odpowiedni styl dzięki klasie disableButton(luckyButton); } else { // Jeśli użytkownik przegrywa i nie jest to ostatnia runda, to otrzymuje adekwatną wiadomość createMessageElement(gameEl, `Niestety, Twoja liczba to: ${userNumber}. Spróbuj jeszcze raz.`); } // Zwiększam liczbę rund o 1 roundsPlayed++; }
Możemy też przenieść wyszukiwanie elementu main poza funkcję playRound. Niepotrzebne jest to, by był wyszukiwany za każdym kliknięciem przycisku „Losuj”.
Dla przećwiczenia tworzenia funkcji podrzędnych możesz przenieść kod z pierwszego warunku (luckyNumber === userNumber) np. do funkcji o nazwie isGameWon.
Kod po refaktoryzacji powinno łatwiej się czytać. Zobacz, czy po usunięciu komentarzy potrafisz zorientować się w działaniu funkcji playRound:
function playRound() { const userNumber = generateRandomNumber(); if (isGameWon(luckyNumber, userNumber)) { const winMsg = createMessageElement( gameEl, `Gratulacje, wygrałeś! Twoja szczęśliwa liczba to: ${userNumber}. Koniec gry.` ); winMsg.style.color = 'red'; disableButton(luckyButton); } else if (isGameLost(roundsPlayed, luckyNumber, userNumber)) { createMessageElement( gameEl, `Niestety, Twoja liczba to: ${userNumber}. Koniec gry.` ); disableButton(luckyButton); } else { createMessageElement( gameEl, `Niestety, Twoja liczba to: ${userNumber}. Spróbuj jeszcze raz.` ); } roundsPlayed++; }
Z czasem dzielenie funkcji na mniejsze będzie przychodzić Ci z łatwością. Pamiętaj, że najważniejsza jest czytelność, nie przejmuj się więc, jeśli po refaktoryzacji przybędzie Ci linii kodu. Nie twórz też „na siłę” elastycznych rozwiązań. Jeżeli programujesz niewielką funkcjonalność, która nie będzie nigdzie reużywana, możesz pozwolić sobie na funkcje służące tylko jednemu celowi (jak np. nasza funkcja isGameLost).
Udostępnij ten artykuł:
Potrzebujesz cotygodniowej dawki motywacji?
Zapisz się i zgarnij za darmo e-book o wartości 39 zł!
PS. Zazwyczaj rozsyłam 1-2 wiadomości na tydzień. Nikomu nie będę udostępniał Twojego adresu email.
Chcesz zostać (lepszym) programistą i lepiej zarabiać?
🚀 Porozmawiajmy o nauce programowania, poszukiwaniu pracy, o rozwoju kariery lub przyszłości branży IT!
Umów się na ✅ bezpłatną i niezobowiązującą rozmowę ze mną.
Chętnie porozmawiam o Twojej przyszłości i pomogę Ci osiągnąć Twoje cele! 🎯