Kurs JavaScript i TypeScript · Moduł 7: Zaawansowany TypeScript
Unikanie głębokiego zagnieżdżania - Early Returns
W tej lekcji6
Dr. Ian Malcolm, siedząc w centrum kontroli Parku Jurajskiego, obserwuje kod napisany przez zespół IT. "Spójrzcie na to," mówi, wskazując na ekran. "To tak jakby chodzić przez labirynt pełen dinozaurów - każdy if to kolejny zakręt, każdy else to ślepa uliczka. Chaos znajduje drogę... ale my nie powinniśmy jej utrudniać!"
Problem: Arrow Anti-Pattern
Jednym z najczęstszych problemów w kodzie początkujących programistów jest tzw. "Arrow Anti-Pattern" lub "Pyramid of Doom" - kod, który zagnieżdża się coraz głębiej, tworząc charakterystyczny kształt strzałki:
// ŹLE: Arrow Anti-Pattern - kod zagnieżdżony jak piramida
function checkDinosaurSafety(dinosaur) {
if (dinosaur) {
if (dinosaur.isAlive) {
if (dinosaur.enclosure) {
if (dinosaur.enclosure.fence) {
if (dinosaur.enclosure.fence.isActive) {
if (dinosaur.health > 50) {
if (dinosaur.aggressionLevel < 7) {
if (dinosaur.lastFed) {
const hoursSinceFeeding = (Date.now() - dinosaur.lastFed) / 3600000;
if (hoursSinceFeeding < 12) {
return 'Bezpieczny';
} else {
return 'Głodny - ostrożnie!';
}
} else {
return 'Brak danych o karmieniu';
}
} else {
return 'Wysoki poziom agresji!';
}
} else {
return 'Niski poziom zdrowia';
}
} else {
return 'ALARM: Ogrodzenie nieaktywne!';
}
} else {
return 'BŁĄD: Brak ogrodzenia';
}
} else {
return 'BŁĄD: Brak przypisanego wybiegu';
}
} else {
return 'Dinozaur martwy';
}
} else {
return 'Brak danych o dinozaurze';
}
}
// Ten kod jest:
// - Trudny do przeczytania (musisz liczyć nawiasy)
// - Trudny do zrozumienia (logika jest rozproszona)
// - Trudny do modyfikacji (dodanie warunku wymaga reorganizacji)
// - Podatny na błędy (łatwo się zgubić w zagnieżdżeniach)Rozwiązanie: Early Returns (Guard Clauses)
Zamiast zagnieżdżać kod, używamy early returns - sprawdzamy warunki błędów na początku i od razu wychodzimy z funkcji. To nazywa się także "guard clauses" (klauzule strażnicze).
// DOBRZE: Early Returns - czytelny kod liniowy
function checkDinosaurSafety(dinosaur) {
// Sprawdzamy wszystkie warunki błędów od razu
if (!dinosaur) {
return 'Brak danych o dinozaurze';
}
if (!dinosaur.isAlive) {
return 'Dinozaur martwy';
}
if (!dinosaur.enclosure) {
return 'BŁĄD: Brak przypisanego wybiegu';
}
if (!dinosaur.enclosure.fence) {
return 'BŁĄD: Brak ogrodzenia';
}
if (!dinosaur.enclosure.fence.isActive) {
return 'ALARM: Ogrodzenie nieaktywne!';
}
if (dinosaur.health <= 50) {
return 'Niski poziom zdrowia';
}
if (dinosaur.aggressionLevel >= 7) {
return 'Wysoki poziom agresji!';
}
if (!dinosaur.lastFed) {
return 'Brak danych o karmieniu';
}
// Główna logika na końcu
const hoursSinceFeeding = (Date.now() - dinosaur.lastFed) / 3600000;
if (hoursSinceFeeding >= 12) {
return 'Głodny - ostrożnie!';
}
return 'Bezpieczny';
}
// Ten kod jest:
// Łatwy do przeczytania (liniowy flow)
// Łatwy do zrozumienia (każdy warunek jest osobno)
// Łatwy do modyfikacji (dodanie warunku = jedna linijka)
// Mniej podatny na błędy (brak zagnieżdżeń)Zasady stosowania Early Returns
1. Sprawdzaj najpierw edge cases i błędy
// ŹLE: Główna logika ukryta w zagnieżdżeniach
function processVisitorEntry(visitor, ticket) {
if (visitor && ticket) {
if (ticket.isValid) {
if (ticket.hasAdultSupervision || visitor.age >= 18) {
if (visitor.hasHealthClearance) {
// Główna logika głęboko zagnieżdżona
registerEntry(visitor);
return true;
}
}
}
}
return false;
}
// DOBRZE: Edge cases na początku
function processVisitorEntry(visitor, ticket) {
// Walidacja danych wejściowych
if (!visitor || !ticket) {
throw new Error('Brak danych odwiedzającego lub biletu');
}
// Sprawdzanie warunków od najbardziej podstawowych
if (!ticket.isValid) {
throw new Error('Bilet nieważny');
}
if (visitor.age < 18 && !ticket.hasAdultSupervision) {
throw new Error('Niepełnoletni wymaga opieki osoby dorosłej');
}
if (!visitor.hasHealthClearance) {
throw new Error('Brak zezwolenia zdrowotnego');
}
// Główna logika na końcu - teraz wiadomo że wszystko jest OK
registerEntry(visitor);
return true;
}2. Używaj negacji dla warunków błędnych
// ŹLE: Pozytywne warunki prowadzą do zagnieżdżeń
function feedDinosaur(dinosaurId, foodAmount) {
if (dinosaurs.has(dinosaurId)) {
const dino = dinosaurs.get(dinosaurId);
if (dino.isAlive) {
if (foodAmount > 0) {
if (foodAmount <= MAX_FOOD_PORTION) {
// Logika karmienia
dino.feed(foodAmount);
return true;
}
}
}
}
return false;
}
// DOBRZE: Negacja warunków pozwala na early return
function feedDinosaur(dinosaurId, foodAmount) {
// Sprawdzamy wszystkie warunki BŁĘDNE i od razu wychodzimy
if (!dinosaurs.has(dinosaurId)) {
throw new Error(`Dinozaur ${dinosaurId} nie istnieje`);
}
const dino = dinosaurs.get(dinosaurId);
if (!dino.isAlive) {
throw new Error('Nie można karmić martwego dinozaura');
}
if (foodAmount <= 0) {
throw new Error('Ilość jedzenia musi być większa niż 0');
}
if (foodAmount > MAX_FOOD_PORTION) {
throw new Error(`Maksymalna porcja to ${MAX_FOOD_PORTION}kg`);
}
// Główna logika - wiemy że wszystko jest OK
dino.feed(foodAmount);
return true;
}3. Wynoś złożoną logikę do osobnych funkcji
// ŹLE: Złożona logika zagnieżdżona
function authorizeAccess(employee, sector) {
if (employee) {
if (employee.isActive) {
if (employee.clearanceLevel >= sector.requiredClearance) {
if (employee.department === sector.allowedDepartment ||
employee.role === 'Admin' ||
employee.temporaryAccess.includes(sector.id)) {
if (!employee.accessRestrictions.includes(sector.id)) {
if (isWithinAllowedHours(employee.schedule, sector.operatingHours)) {
return grantAccess(employee, sector);
}
}
}
}
}
}
return denyAccess(employee, sector);
}
// DOBRZE: Wydzielamy warunki do funkcji pomocniczych
function authorizeAccess(employee, sector) {
if (!employee) {
return denyAccess(null, sector, 'Brak danych pracownika');
}
if (!employee.isActive) {
return denyAccess(employee, sector, 'Konto nieaktywne');
}
if (!hasSufficientClearance(employee, sector)) {
return denyAccess(employee, sector, 'Niewystarczający poziom dostępu');
}
if (!isDepartmentAuthorized(employee, sector)) {
return denyAccess(employee, sector, 'Nieautoryzowany dział');
}
if (hasAccessRestriction(employee, sector)) {
return denyAccess(employee, sector, 'Ograniczenie dostępu');
}
if (!isWithinAllowedHours(employee.schedule, sector.operatingHours)) {
return denyAccess(employee, sector, 'Poza godzinami dostępu');
}
return grantAccess(employee, sector);
}
// Funkcje pomocnicze - każda ma pojedynczą odpowiedzialność
function hasSufficientClearance(employee, sector) {
return employee.clearanceLevel >= sector.requiredClearance;
}
function isDepartmentAuthorized(employee, sector) {
return employee.department === sector.allowedDepartment ||
employee.role === 'Admin' ||
employee.temporaryAccess.includes(sector.id);
}
function hasAccessRestriction(employee, sector) {
return employee.accessRestrictions.includes(sector.id);
}Praktyczne przykłady refaktoryzacji
Przykład 1: System alarmowy
// PRZED: Zagnieżdżony kod trudny do czytania
function triggerAlarm(sensorData) {
if (sensorData) {
if (sensorData.fence) {
if (sensorData.fence.voltage < MIN_VOLTAGE) {
if (emergencyProtocol.isActive) {
if (securityTeam.isAvailable) {
if (evacuationRoutes.areOpen) {
activateFullAlarm();
notifySecurityTeam();
return { status: 'active', level: 'critical' };
} else {
activatePartialAlarm();
return { status: 'active', level: 'warning' };
}
} else {
activateAutomatedResponse();
return { status: 'automated', level: 'critical' };
}
} else {
logIncident();
return { status: 'logged', level: 'info' };
}
} else {
return { status: 'normal', level: 'info' };
}
}
}
return { status: 'error', level: 'critical' };
}
// PO: Czytelny kod z early returns
function triggerAlarm(sensorData) {
// Walidacja danych
if (!sensorData || !sensorData.fence) {
return { status: 'error', level: 'critical', message: 'Brak danych z czujnika' };
}
// Sprawdzenie głównego warunku
if (sensorData.fence.voltage >= MIN_VOLTAGE) {
return { status: 'normal', level: 'info', message: 'Napięcie prawidłowe' };
}
// Niskie napięcie wykryte - sprawdzamy protokoły
if (!emergencyProtocol.isActive) {
logIncident();
return { status: 'logged', level: 'info', message: 'Incydent zarejestrowany' };
}
// Protokół aktywny - sprawdzamy dostępność zespołu
if (!securityTeam.isAvailable) {
activateAutomatedResponse();
return { status: 'automated', level: 'critical', message: 'Odpowiedź automatyczna' };
}
// Zespół dostępny - sprawdzamy drogi ewakuacyjne
if (!evacuationRoutes.areOpen) {
activatePartialAlarm();
return { status: 'active', level: 'warning', message: 'Alarm częściowy' };
}
// Wszystko gotowe - pełny alarm
activateFullAlarm();
notifySecurityTeam();
return { status: 'active', level: 'critical', message: 'Pełny alarm aktywowany' };
}Przykład 2: Walidacja formularza
// PRZED: Walidacja z wieloma poziomami zagnieżdżeń
function validateVisitorRegistration(formData) {
if (formData.firstName) {
if (formData.lastName) {
if (formData.email) {
if (isValidEmail(formData.email)) {
if (formData.age) {
if (formData.age >= 5 && formData.age <= 120) {
if (formData.emergencyContact) {
if (formData.emergencyContact.phone) {
if (isValidPhone(formData.emergencyContact.phone)) {
if (formData.acceptedTerms) {
return { valid: true, data: formData };
} else {
return { valid: false, error: 'Musisz zaakceptować regulamin' };
}
} else {
return { valid: false, error: 'Nieprawidłowy numer telefonu awaryjnego' };
}
} else {
return { valid: false, error: 'Brak numeru telefonu awaryjnego' };
}
} else {
return { valid: false, error: 'Brak kontaktu awaryjnego' };
}
} else {
return { valid: false, error: 'Wiek musi być między 5 a 120' };
}
} else {
return { valid: false, error: 'Brak wieku' };
}
} else {
return { valid: false, error: 'Nieprawidłowy adres email' };
}
} else {
return { valid: false, error: 'Brak adresu email' };
}
} else {
return { valid: false, error: 'Brak nazwiska' };
}
} else {
return { valid: false, error: 'Brak imienia' };
}
}
// PO: Czytelna walidacja z early returns
function validateVisitorRegistration(formData) {
// Walidacja wymaganych pól tekstowych
if (!formData.firstName) {
return { valid: false, error: 'Brak imienia' };
}
if (!formData.lastName) {
return { valid: false, error: 'Brak nazwiska' };
}
if (!formData.email) {
return { valid: false, error: 'Brak adresu email' };
}
if (!isValidEmail(formData.email)) {
return { valid: false, error: 'Nieprawidłowy adres email' };
}
// Walidacja wieku
if (!formData.age) {
return { valid: false, error: 'Brak wieku' };
}
if (formData.age < 5 || formData.age > 120) {
return { valid: false, error: 'Wiek musi być między 5 a 120' };
}
// Walidacja kontaktu awaryjnego
if (!formData.emergencyContact) {
return { valid: false, error: 'Brak kontaktu awaryjnego' };
}
if (!formData.emergencyContact.phone) {
return { valid: false, error: 'Brak numeru telefonu awaryjnego' };
}
if (!isValidPhone(formData.emergencyContact.phone)) {
return { valid: false, error: 'Nieprawidłowy numer telefonu awaryjnego' };
}
// Walidacja akceptacji regulaminu
if (!formData.acceptedTerms) {
return { valid: false, error: 'Musisz zaakceptować regulamin' };
}
// Wszystkie walidacje przeszły
return { valid: true, data: formData };
}Przykład 3: Przetwarzanie zamówienia
// PRZED: Złożona logika biznesowa w zagnieżdżeniach
function processFoodOrder(order) {
if (order && order.items && order.items.length > 0) {
if (inventory.checkAvailability(order.items)) {
const total = calculateTotal(order.items);
if (order.payment) {
if (order.payment.amount >= total) {
if (order.deliveryLocation) {
if (isDeliveryLocationSafe(order.deliveryLocation)) {
if (deliveryPersonnel.isAvailable()) {
const receipt = generateReceipt(order, total);
inventory.reduceStock(order.items);
scheduleDelivery(order);
return { success: true, receipt };
} else {
return { success: false, reason: 'Brak dostępnego personelu' };
}
} else {
return { success: false, reason: 'Niebezpieczna lokalizacja dostawy' };
}
} else {
return { success: false, reason: 'Brak lokalizacji dostawy' };
}
} else {
return { success: false, reason: 'Niewystarczająca kwota płatności' };
}
} else {
return { success: false, reason: 'Brak danych płatności' };
}
} else {
return { success: false, reason: 'Produkty niedostępne w magazynie' };
}
} else {
return { success: false, reason: 'Nieprawidłowe zamówienie' };
}
}
// PO: Logika biznesowa podzielona na kroki
function processFoodOrder(order) {
// Krok 1: Walidacja zamówienia
const validationError = validateOrder(order);
if (validationError) {
return { success: false, reason: validationError };
}
// Krok 2: Sprawdzenie dostępności w magazynie
if (!inventory.checkAvailability(order.items)) {
return { success: false, reason: 'Produkty niedostępne w magazynie' };
}
// Krok 3: Weryfikacja płatności
const total = calculateTotal(order.items);
const paymentError = validatePayment(order.payment, total);
if (paymentError) {
return { success: false, reason: paymentError };
}
// Krok 4: Weryfikacja dostawy
const deliveryError = validateDelivery(order.deliveryLocation);
if (deliveryError) {
return { success: false, reason: deliveryError };
}
if (!deliveryPersonnel.isAvailable()) {
return { success: false, reason: 'Brak dostępnego personelu' };
}
// Krok 5: Realizacja zamówienia
const receipt = generateReceipt(order, total);
inventory.reduceStock(order.items);
scheduleDelivery(order);
return { success: true, receipt };
}
// Funkcje pomocnicze
function validateOrder(order) {
if (!order) return 'Brak zamówienia';
if (!order.items || order.items.length === 0) return 'Brak produktów w zamówieniu';
return null;
}
function validatePayment(payment, requiredAmount) {
if (!payment) return 'Brak danych płatności';
if (payment.amount < requiredAmount) return 'Niewystarczająca kwota płatności';
return null;
}
function validateDelivery(location) {
if (!location) return 'Brak lokalizacji dostawy';
if (!isDeliveryLocationSafe(location)) return 'Niebezpieczna lokalizacja dostawy';
return null;
}Dodatkowe techniki
1. Używaj optional chaining i nullish coalescing
// PRZED: Zagnieżdżone sprawdzanie null/undefined
function getDinosaurLocation(dinosaurId) {
const dino = dinosaurs.get(dinosaurId);
if (dino) {
if (dino.enclosure) {
if (dino.enclosure.location) {
return dino.enclosure.location.coordinates;
}
}
}
return null;
}
// PO: Optional chaining
function getDinosaurLocation(dinosaurId) {
const dino = dinosaurs.get(dinosaurId);
return dino?.enclosure?.location?.coordinates ?? null;
}2. Wydzielaj złożone warunki do zmiennych
// PRZED: Trudny do zrozumienia warunek
function shouldEvacuate(park) {
if ((park.fenceStatus === 'breached' && park.dangerousDinosaurs > 0) ||
(park.powerOutage && park.backupGeneratorFailed) ||
(park.weatherAlert === 'severe' && park.visitorCount > 100)) {
return true;
}
return false;
}
// PO: Nazwane warunki
function shouldEvacuate(park) {
const hasDangerousBreach = park.fenceStatus === 'breached' && park.dangerousDinosaurs > 0;
const hasTotalPowerFailure = park.powerOutage && park.backupGeneratorFailed;
const hasWeatherEmergency = park.weatherAlert === 'severe' && park.visitorCount > 100;
return hasDangerousBreach || hasTotalPowerFailure || hasWeatherEmergency;
}3. Używaj strategii polimorfizmu zamiast switch/if-else
// PRZED: Długi łańcuch if-else
function handleDinosaurBehavior(dinosaur, behavior) {
if (behavior === 'aggressive') {
activateDefenses();
alertSecurity();
evacuateVisitors();
} else if (behavior === 'scared') {
reduceNoiseLevel();
increaseDistance();
} else if (behavior === 'hungry') {
prepareFoodration();
scheduleFeedingTime();
} else if (behavior === 'sleeping') {
reduceActivity();
dimLights();
} else if (behavior === 'playful') {
allowInteraction();
monitorClosely();
} else {
logUnknownBehavior(behavior);
}
}
// PO: Strategia z mapą funkcji
const behaviorHandlers = {
aggressive: () => {
activateDefenses();
alertSecurity();
evacuateVisitors();
},
scared: () => {
reduceNoiseLevel();
increaseDistance();
},
hungry: () => {
prepareFoodRation();
scheduleFeedingTime();
},
sleeping: () => {
reduceActivity();
dimLights();
},
playful: () => {
allowInteraction();
monitorClosely();
}
};
function handleDinosaurBehavior(dinosaur, behavior) {
const handler = behaviorHandlers[behavior];
if (!handler) {
logUnknownBehavior(behavior);
return;
}
handler();
}Podsumowanie
"Życie... znajduje drogę," mówi Dr. Malcolm. "Ale kod nie powinien być labiryntem. Każda funkcja powinna mieć jasną, liniową ścieżkę - od walidacji na początku do logiki biznesowej na końcu."
Kluczowe zasady:
- Sprawdzaj błędy najpierw - używaj early returns dla edge cases
- Neguj warunki - zamiast
if (x)użyjif (!x) return - Unikaj zagnieżdżeń - maksymalnie 2-3 poziomy
- Wydzielaj logikę - złożone warunki → pomocnicze funkcje
- Nazywaj warunki - przypisuj złożone wyrażenia do zmiennych
- Używaj nowoczesnego JS - optional chaining, nullish coalescing
Kod z early returns jest:
- Czytelniejszy - łatwiej zrozumieć flow
- Łatwiejszy w utrzymaniu - łatwo dodać nowy warunek
- Mniej podatny na błędy - mniej miejsc gdzie coś może pójść nie tak
- Bardziej testowalny - każdy warunek można przetestować osobno
Pamiętaj: dobry kod to nie tylko kod, który działa - to kod, który jest łatwy do zrozumienia i modyfikacji. A w Parku Jurajskim, gdzie każdy błąd może mieć katastrofalne skutki, czytelność kodu może uratować życie!
Kod do tej lekcji: index.ts
// Unikanie glebokiego zagniezdzania - Early Returns
// Park Jurajski: System bezpieczenstwa z guard clauses
console.log("=== EARLY RETURNS - PARK JURAJSKI ===\n");
// ===========================================
// 1. Przyklad: Sprawdzanie bezpieczenstwa dinozaura
// ===========================================
console.log("=== 1. SPRAWDZANIE BEZPIECZENSTWA ===");
interface DinosaurData {
name: string;
isAlive: boolean;
health: number;
aggressionLevel: number;
enclosure?: {
fence?: {
isActive: boolean;
voltage: number;
};
};
lastFed?: number;
}
// Early returns - kazdy warunek sprawdzany osobno
function checkSafety(dino: DinosaurData): string {
if (!dino) {
return "BLAD: Brak danych o dinozaurze";
}
if (!dino.isAlive) {
return "Dinozaur martwy - brak zagrozenia";
}
if (!dino.enclosure?.fence) {
return "ALARM: Brak ogrodzenia!";
}
if (!dino.enclosure.fence.isActive) {
return "ALARM: Ogrodzenie nieaktywne!";
}
if (dino.health <= 30) {
return "OSTRZEZENIE: Niski poziom zdrowia";
}
if (dino.aggressionLevel >= 8) {
return "ALARM: Bardzo wysoki poziom agresji!";
}
return "Status: Bezpieczny";
}
const trex: DinosaurData = {
name: "T-Rex",
isAlive: true,
health: 85,
aggressionLevel: 6,
enclosure: { fence: { isActive: true, voltage: 10000 } },
lastFed: Date.now() - 3600000
};
const brokenFence: DinosaurData = {
name: "Velociraptor",
isAlive: true,
health: 90,
aggressionLevel: 9,
enclosure: { fence: { isActive: false, voltage: 0 } }
};
console.log(`${trex.name}: ${checkSafety(trex)}`);
console.log(`${brokenFence.name}: ${checkSafety(brokenFence)}`);
// ===========================================
// 2. Walidacja z early returns
// ===========================================
console.log("\n=== 2. WALIDACJA FORMULARZA ===");
interface VisitorForm {
firstName?: string;
lastName?: string;
age?: number;
email?: string;
acceptedTerms?: boolean;
}
function validateVisitor(form: VisitorForm): { valid: boolean; error?: string } {
if (!form.firstName) {
return { valid: false, error: "Brak imienia" };
}
if (!form.lastName) {
return { valid: false, error: "Brak nazwiska" };
}
if (!form.age || form.age < 5) {
return { valid: false, error: "Minimalny wiek: 5 lat" };
}
if (!form.email || !form.email.includes("@")) {
return { valid: false, error: "Nieprawidlowy email" };
}
if (!form.acceptedTerms) {
return { valid: false, error: "Wymagana akceptacja regulaminu" };
}
return { valid: true };
}
const goodForm: VisitorForm = {
firstName: "Jan",
lastName: "Kowalski",
age: 25,
email: "jan@park.com",
acceptedTerms: true
};
const badForm: VisitorForm = {
firstName: "Anna",
age: 3
};
console.log("Poprawny formularz:", validateVisitor(goodForm));
console.log("Niepoprawny formularz:", validateVisitor(badForm));
// ===========================================
// 3. TODO: Sprobuj sam!
// ===========================================
console.log("\n=== 3. TWOJA KOLEJ ===");
console.log("Refaktoryzuj ponizszy kod uzywajac early returns:");
// TODO: Przepisz te funkcje uzywajac early returns
// zamiast zagniezdzonych if-else
function processOrder(items: string[], payment: number) {
// Uzyj early returns zamiast zagniezdzonych warunkow!
if (items && items.length > 0) {
if (payment > 0) {
if (payment >= items.length * 10) {
console.log("Zamowienie przetworzone!");
return true;
} else {
console.log("Za mala kwota");
return false;
}
} else {
console.log("Brak platnosci");
return false;
}
} else {
console.log("Brak produktow");
return false;
}
}
processOrder(["Bilet VIP", "Lunch"], 50);
processOrder([], 10);
processOrder(["Bilet"], 5);
console.log("\nWskazowka: Uzyj negacji warunkow i return na poczatku!");Hasła z dokumentacji w tej lekcji
Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Czym jest tzw. „Arrow Anti-Pattern” (Pyramid of Doom) w kodzie?
2. Co zwróci wyrażenie
dino?.enclosure?.location ?? null, gdydinojestundefined?
Zadania praktyczne w grze
- Układanie w pionie
Uporządkuj kolejność elementów w funkcji stosującej early returns (od góry):
- Klikanie w kolejności
Ułóż elementy klauzuli strażniczej (guard clause):