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!

Jak dzielić funkcje JavaScript na mniejsze

Poradnik dla początkujących programistów

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.

Spis treści

 Przykładowy kod – gra JavaScript

Ż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:

  1. Wyświetlamy użytkownikowi losowy numer z zakresu 1-5.
  2. Gdy użytkownik kilka „Losuj”, losujemy dla niego randomowy numer z zakresu 1-5.
  3. Użytkownik ma 3 próby. Jeśli wygra, to rozgrywka kończy się w tym momencie. Jeśli przegra, próbuje ponownie, aż 3 rundy się skończą.
Działanie gry podejrzysz dzięki GitHub Pages, a kod całego rozwiązania przed refaktoryzacją i po niej znajdziesz w tym repozytorium.
<!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++;
  }
});

 Rozbudowana funkcja JS – grupowanie kodu

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:

  • losowanie randomowej liczby z zakresu 1-5,
  • blokowanie przycisku losowania i nadanie mu odpowiedniej klasy.

Zacznijmy więc od tego i zobaczmy, jak „odchudzi się” nasza funkcja playRound.

 Jak przenieść powielany kod do mniejszej funkcji

 Funkcja losująca randomowy numer

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;
}

 Funkcja blokująca przycisk

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);
//...

 Jak przenieść fragmenty kodu do nowych funkcji

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.

 Generowanie elementu DOM – wiadomość dla użytkownika

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.

 Instrukcja warunkowa – kod warunku w osobnej 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)) {
   // ...
 }

 Dalsza refaktoryzacja funkcji JavaScript 

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.

 Refaktoryzacja – sposób na poprawę implementacji

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.

Działanie gry podejrzysz dzięki GitHub Pages, a kod całego rozwiązania przed refaktoryzacją i po niej znajdziesz w tym repozytorium.

 Podsumowanie – jak dzielić funkcje JS na mniejsze

  • Grupowanie – grupuj (wyodrębniaj) poszczególne zadania wewnątrz funkcji. Możesz najpierw opisać je sobie komentarzami.
  • Nazewnictwo – funkcje podrzędne nazywaj tak, aby było wiadomo, za co odpowiadają. Przedrostek is stosuj, gdy zwracana przez funkcję wartość jest typu boolean (true lub false). Jeśli nasuwa Ci się na myśli przedrostek are, to możliwe, że funkcję da się podzielić jeszcze bardziej.
  • Przenoszenie kodu – przyklejaj fragment kodu z funkcji nadrzędnej do mniejszej funkcji. Kod, który docelowo usuniesz z funkcji nadrzędnej, tymczasowo zakomentuj (gdyby coś nie działało, łatwo będzie do niego wrócić).
  • Parametry funkcji – po przeklejeniu fragmentu kodu zobacz, czego w ciele funkcji brakuje: jakich danych funkcja potrzebuje z zewnątrz, aby mogła zadziałać? Właśnie te rzeczy będą musiały zostać przekazane do funkcji jako argumenty. Staraj się unikać korzystania ze zmiennych globalnych.
  • Wynik działania funkcji – jeżeli będziesz korzystać z wartości zwracanej przez funkcję, to pamiętaj, by na końcu ciała funkcji zwrócić tę wartość, korzystając ze słówka return.
  • Wywołanie funkcji podrzędnej – wywołaj ją wewnątrz funkcji nadrzędnej w miejscu „usuniętego” kodu. Jeżeli funkcja przyjmuje jakieś argumenty, to pamiętaj, by je przekazać.
  • Częste sprawdzanie kodu – po każdym wydzieleniu funkcji sprawdzaj, czy kod działa. W przeciwnym razie trudniej będzie Ci namierzyć błąd – im więcej niesprawdzonych funkcji, tym trudniej zidentyfikować, w której jest problem.
  • Formatowanie kodu – funkcję nadrzędną umieść w pliku wyżej, a podrzędne niżej. Jeżeli prawidłowo dobierzesz nazwy funkcji zagnieżdżonych, to będzie łatwo zorientować się w działaniu funkcji nadrzędnej. Jeśli jednak ktoś będzie potrzebował zagłębić się w kod, szybko znajdzie go niżej.

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ł:

Mentoring to efektywna nauka pod okiem doświadczonej osoby, która:

  • przekazuje Ci swoją wiedzę i nadzoruje Twoje postępy w zdobywaniu umiejętności,
  • uczy Cię dobrych praktyk i wyłapuje złe nawyki,
  • wspiera Twój rozwój i zwiększa zaangażowanie w naukę.

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! 🎯