Kurs JavaScript i TypeScript · Moduł 5: Zaawansowany JavaScript

Breakpointy i krokowe wykonanie kodu

12 min czytania
W tej lekcji8

W poprzednim ćwiczeniu poznaliśmy konsolę przeglądarki i jej funkcje do logowania informacji. Jednak nawet najlepsze komunikaty w konsoli mogą być niewystarczające, gdy mamy do czynienia ze skomplikowanymi błędami w systemie zarządzania Parkiem Jurajskim. Co jeśli funkcja validateDinosaurContainment() zwraca nieprawidłowy wynik, ale nie wiemy dlaczego? Co jeśli algorytm śledzenia zachowań drapieżników zawiera subtelny błąd?

W takich przypadkach potrzebujemy możliwości wstrzymania wykonania kodu w określonym miejscu i przeanalizowania stanu aplikacji. Tutaj z pomocą przychodzą breakpointy i krokowe wykonanie kodu - jedne z najpotężniejszych technik debugowania aplikacji JavaScript.

Czym są breakpointy?

Breakpoint (punkt wstrzymania) to znacznik umieszczony w kodzie, który mówi przeglądarce, aby zatrzymała wykonanie kodu, gdy dotrze do tego miejsca. Pozwala to na dokładne zbadanie stanu aplikacji w wybranym momencie - możemy sprawdzić wartości zmiennych, obejrzeć stos wywołań i wykonać kod krok po kroku.

Ustawianie breakpointów w przeglądarce

Istnieje kilka sposobów ustawiania breakpointów w przeglądarce:

1. Breakpointy w Panelu Sources/Debugger

Najprostszym sposobem jest skorzystanie z panelu Sources (Chrome) lub Debugger (Firefox):

  1. Otwórz narzędzia deweloperskie (<kbd>F12</kbd> lub <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>I</kbd>)
  2. Przejdź do zakładki "Sources" (Chrome) lub "Debugger" (Firefox)
  3. Znajdź i otwórz plik, w którym chcesz ustawić breakpoint
  4. Kliknij numer linii, w której chcesz zatrzymać wykonanie kodu
  5. W wybranym miejscu pojawi się niebieska strzałka lub znacznik, wskazując aktywny breakpoint

Na przykład, możemy chcieć zatrzymać wykonanie kodu w momencie, gdy sprawdzamy status ogrodzenia w Parku Jurajskim:

1function checkFenceStatus(sectorId) {
2  const sector = getSector(sectorId); // Ustawiamy breakpoint w tej linii
3
4  if (!sector) {
5    throw new Error(`Nieznany sektor: ${sectorId}`);
6  }
7
8  const fenceStatus = sector.fence.getStatus();
9
10  if (fenceStatus !== 'active') {
11    triggerAlarm(sectorId, `Awaria ogrodzenia: ${fenceStatus}`);
12  }
13
14  return fenceStatus;
15}

Po ustawieniu breakpointa, gdy funkcja checkFenceStatus() zostanie wywołana, przeglądarka zatrzyma wykonanie kodu w tej linii, a my będziemy mogli sprawdzić wartość sectorId i inne dostępne zmienne.

2. Programowe breakpointy z instrukcją debugger

Czasami wygodniejsze jest umieszczenie breakpointu bezpośrednio w kodzie, używając instrukcji debugger:

1function trackDinosaur(dinosaurId, location) {
2  const dinosaur = getDinosaurById(dinosaurId);
3
4  debugger; // Kod zatrzyma się tutaj, gdy funkcja zostanie wywołana
5
6  if (!dinosaur) {
7    console.error(`Nie znaleziono dinozaura o ID: ${dinosaurId}`);
8    return false;
9  }
10
11  updateDinosaurLocation(dinosaurId, location);
12  checkContainmentStatus(dinosaurId, location);
13
14  return true;
15}

Instrukcja debugger zostanie zignorowana, jeśli narzędzia deweloperskie nie są otwarte, ale gdy są aktywne, przeglądarka zatrzyma wykonanie kodu w tym miejscu.

3. Warunkowe breakpointy

Czasem chcemy zatrzymać wykonanie kodu tylko wtedy, gdy spełniony jest określony warunek. Na przykład, gdy śledzimy dinozaura o konkretnym ID lub gdy poziom zagrożenia przekracza określoną wartość:

Ustawianie w interfejsie przeglądarki

  1. Ustaw zwykły breakpoint, klikając numer linii
  2. Kliknij prawym przyciskiem myszy na breakpoint
  3. Wybierz opcję "Edit breakpoint" lub "Add condition"
  4. Wprowadź warunek, na przykład: dinosaurId === 'TRX-01' lub dangerLevel > 8

Programowe warunkowe zatrzymanie

Możemy również użyć konstrukcji warunkowej z instrukcją debugger:

1function monitorDinosaurBehavior(dinosaur) {
2  const behaviorPattern = analyzeBehavior(dinosaur);
3  const dangerLevel = assessThreatLevel(behaviorPattern);
4
5  // Zatrzymaj wykonanie tylko jeśli poziom zagrożenia jest wysoki
6  if (dangerLevel > 8) {
7    debugger; // Zatrzyma się tylko dla wysokiego poziomu zagrożenia
8
9    // Kod do obsługi niebezpiecznych zachowań
10    activateEmergencyProtocol(dinosaur.id, dangerLevel);
11  }
12
13  return {
14    dinosaurId: dinosaur.id,
15    behaviorPattern,
16    dangerLevel,
17    timestamp: new Date()
18  };
19}

Krokowe wykonanie kodu (Step Execution)

Gdy kod jest zatrzymany na breakpoincie, mamy kilka opcji krokowego wykonania, które pozwalają na kontrolowane przejście przez kod:

1. Step Over (Krok nad / F10)

Wykonuje aktualną linię kodu i zatrzymuje się na następnej linii w tej samej funkcji. Jeśli aktualna linia zawiera wywołanie funkcji, funkcja ta jest wykonywana w całości (nie zatrzymujemy się wewnątrz niej).

1function monitorParkSystems() {
2  checkSecuritySystems();     // Step Over wykona całą tę funkcję...
3  checkDinosaurContainment(); // ...i zatrzyma się na tej linii
4  checkVisitorSafety();
5}

2. Step Into (Krok do / F11)

Wykonuje aktualną linię kodu, ale jeśli linia zawiera wywołanie funkcji, debugger "wchodzi" do tej funkcji i zatrzymuje się na jej pierwszej linii.

1function monitorParkSystems() {
2  checkSecuritySystems();     // Step Into "wejdzie" do tej funkcji...
3  checkDinosaurContainment(); // ...zamiast przejść od razu tutaj
4  checkVisitorSafety();
5}
6
7function checkSecuritySystems() {
8  const fenceStatus = checkFences();   // ...i zatrzyma się tutaj
9  const cameraStatus = checkCameras();
10  const gateStatus = checkGates();
11
12  return { fenceStatus, cameraStatus, gateStatus };
13}

3. Step Out (Krok na zewnątrz / Shift+F11)

Wykonuje pozostałą część aktualnej funkcji i zatrzymuje się po wyjściu z funkcji, w miejscu jej wywołania.

1function checkSecuritySystems() {
2  const fenceStatus = checkFences();
3  const cameraStatus = checkCameras(); // Jeśli jesteśmy tutaj...
4  const gateStatus = checkGates();
5
6  return { fenceStatus, cameraStatus, gateStatus };
7}
8
9function monitorParkSystems() {
10  checkSecuritySystems();     // ...Step Out wykona resztę funkcji i zatrzyma się tutaj
11  checkDinosaurContainment();
12  checkVisitorSafety();
13}

4. Continue (Kontynuuj / F8)

Wznawia wykonanie kodu aż do napotkania następnego breakpointa lub zakończenia skryptu.

Inspekcja zmiennych i stanu

Gdy kod jest zatrzymany na breakpoincie, możemy szczegółowo zbadać stan aplikacji:

1. Panel Scope/Variables

Jeden z najważniejszych paneli podczas debugowania - pokazuje wszystkie dostępne zmienne w aktualnym zasięgu (scope):

  • Local - zmienne lokalne w aktualnej funkcji
  • Closure - zmienne z domknięć (funkcji nadrzędnych)
  • Global - zmienne globalne
  • this - wartość this w aktualnym kontekście

Na przykład, gdy debugujemy funkcję monitorującą zachowanie dinozaurów:

1function analyzeDinosaurBehavior(dinosaur, environmentalFactors) {
2  const { species, previousBehaviors, lastFeeding } = dinosaur;
3  const { weather, timeOfDay, nearbyDinosaurs } = environmentalFactors;
4
5  const timeSinceFeeding = Date.now() - lastFeeding;
6  const isHungry = timeSinceFeeding > getSpeciesFeedingThreshold(species);
7
8  debugger; // zatrzymaj się tutaj
9
10  // W tym momencie w panelu Scope zobaczymy:
11  // - Local: dinosaur, environmentalFactors, species, previousBehaviors, lastFeeding,
12  //          weather, timeOfDay, nearbyDinosaurs, timeSinceFeeding, isHungry
13  // - Global: globalne obiekty i funkcje
14
15  const behaviorPrediction = predictBehavior(
16    species,
17    previousBehaviors,
18    isHungry,
19    weather,
20    timeOfDay,
21    nearbyDinosaurs
22  );
23
24  return {
25    dinosaurId: dinosaur.id,
26    prediction: behaviorPrediction,
27    riskLevel: assessRiskLevel(behaviorPrediction),
28    isHungry,
29    timestamp: new Date()
30  };
31}

2. Watch Expressions (Wyrażenia obserwowane)

Panel Watch pozwala na dodanie wyrażeń, które chcemy monitorować podczas debugowania. Jest to szczególnie przydatne dla złożonych wyrażeń lub wartości, które nie są bezpośrednio widoczne w panelu Scope:

1// Możemy dodać wyrażenia obserwowane takie jak:
2dinosaur.species === 'Velociraptor'
3getDistanceFromFence(dinosaur.location)
4isInDangerZone(dinosaur.location, restrictedSectors)

3. Konsola w trybie debugowania

W trakcie zatrzymania na breakpoincie, konsola działa w kontekście aktualnej pozycji w kodzie. Możemy więc wykonywać wyrażenia, które mają dostęp do wszystkich zmiennych widocznych w bieżącym zasięgu:

1// W konsoli możemy wpisać:
2dinosaur.species
3timeSinceFeeding / (1000 * 60 * 60) // Konwersja milisekund na godziny
4nearbyDinosaurs.filter(d => d.species === 'Velociraptor')

Jest to potężny sposób na interaktywne badanie stanu aplikacji i wykonywanie eksperymentów bez modyfikowania kodu.

Praktyczne techniki debugowania dla Parku Jurajskiego

1. Debugowanie asynchronicznego kodu

Systemy w Parku Jurajskim często wykorzystują asynchroniczne operacje - pobieranie danych z czujników, komunikacja z systemami bezpieczeństwa, etc. Debugowanie takiego kodu wymaga specjalnych technik:

1async function monitorsEnvironmentalSensors() {
2  try {
3    console.log("Rozpoczęcie monitorowania czujników...");
4
5    // Asynchroniczne pobranie danych z czujników
6    const temperatureData = await fetchSensorData('temperature');
7    debugger; // Breakpoint zatrzyma się po otrzymaniu odpowiedzi
8
9    const humidityData = await fetchSensorData('humidity');
10    const windData = await fetchSensorData('wind');
11
12    // Analiza danych środowiskowych
13    const environmentalRisk = analyzeEnvironmentalRisk(
14      temperatureData,
15      humidityData,
16      windData
17    );
18
19    if (environmentalRisk.level === 'high') {
20      notifyParkManagement(
21        `Wykryto wysokie ryzyko środowiskowe: ${environmentalRisk.reason}`
22      );
23    }
24
25    return environmentalRisk;
26  } catch (error) {
27    console.error("Błąd monitorowania czujników:", error);
28    triggerBackupSystems();
29    return { level: 'unknown', reason: 'error' };
30  }
31}

W narzędziach deweloperskich nowoczesnych przeglądarek, breakpointy działają poprawnie z asynchronicznymi funkcjami - wykonanie zatrzyma się w punkcie debugger, nawet po operacjach await.

2. Zatrzymywanie na wyjątkach (Exceptions)

W złożonym systemie jakim jest Park Jurajski, błędy mogą pojawiać się w nieoczekiwanych miejscach. Zamiast ręcznie ustawiać breakpointy, możemy skonfigurować debugger, aby zatrzymywał się, gdy wystąpi wyjątek:

  1. Otwórz panel Sources/Debugger
  2. Znajdź sekcję "Breakpoints" (lub podobną)
  3. Zaznacz opcję "Pause on exceptions" (Zatrzymaj na wyjątkach)
  4. Opcjonalnie: zaznacz "Pause on caught exceptions" (Zatrzymaj na obsłużonych wyjątkach)

Teraz przeglądarka zatrzyma wykonanie kodu, gdy wystąpi jakikolwiek nieobsłużony wyjątek, co znacznie ułatwia identyfikację źródła problemów.

3. Mapowanie źródeł (Source Maps)

W produkcyjnych aplikacjach kod JavaScript jest często minifikowany i bundlowany, co utrudnia debugowanie. Mapowanie źródeł pozwala przeglądarkom pokazywać i debugować oryginalny kod źródłowy, nawet gdy wykonywany jest kod zminifikowany:

1// webpack.config.js
2module.exports = {
3  //
4  devtool: 'source-map', // Generuje mapy źródeł dla wszystkich plików
5  //
6};

Dla Parku Jurajskiego, gdzie bezpieczeństwo jest priorytetem, to kluczowa funkcja umożliwiająca szybkie debugowanie produkcyjnych problemów.

4. Logowanie punktów kontrolnych

Czasami, zwłaszcza w złożonych systemach, warto łączyć breakpointy z logowaniem, aby śledzić wykonanie kodu:

1function monitorDinosaurMovement(dinosaurId) {
2  const dinosaur = getDinosaurById(dinosaurId);
3  console.log(`[Checkpoint 1] Rozpoczęcie monitorowania ${dinosaur.name}`);
4
5  if (!dinosaur.tracker.isActive) {
6    console.warn(`[Checkpoint 2A] Nieaktywny tracker dla ${dinosaur.name}`);
7    activateTracker(dinosaurId);
8    debugger; // Zatrzymaj się tylko, gdy tracker był nieaktywny
9  } else {
10    console.log(`[Checkpoint 2B] Tracker aktywny dla ${dinosaur.name}`);
11  }
12
13  const location = getLastKnownLocation(dinosaurId);
14  console.log(`[Checkpoint 3] Ostatnia lokalizacja: ${JSON.stringify(location)}`);
15
16  // Różne ścieżki bazujące na lokalizacji
17  if (isInRestrictedZone(location)) {
18    console.error(`[Checkpoint 4A] Dinozaur w strefie ograniczonej!`);
19    debugger; // Zawsze zatrzymaj się w przypadku naruszenia strefy
20    triggerContainmentProtocol(dinosaurId, location);
21  } else if (isApproachingFence(location)) {
22    console.warn(`[Checkpoint 4B] Dinozaur zbliża się do ogrodzenia`);
23    increaseSurveillance(dinosaurId);
24  } else {
25    console.log(`[Checkpoint 4C] Dinozaur w normalnej strefie`);
26  }
27
28  return {
29    dinosaurId,
30    location,
31    status: getCurrentStatus(dinosaurId)
32  };
33}

Taki wzorzec "checkpointów" pomaga śledzić przepływ wykonania i szybko identyfikować, która ścieżka kodu została wykonana, nawet gdy nie korzystamy aktywnie z debuggera.

Debugowanie w praktyce - analiza przypadku

Przeanalizujmy praktyczny przykład debugowania problemu w systemie monitorowania dinozaurów:

1class DinosaurMonitoringSystem {
2  constructor() {
3    this.activeMonitors = new Map();
4    this.alertLevel = 'normal';
5    this.lastScanTime = null;
6  }
7
8  startMonitoring(dinosaurId) {
9    if (this.activeMonitors.has(dinosaurId)) {
10      console.log(`Monitoring dla dinozaura ${dinosaurId} już aktywny`);
11      return false;
12    }
13
14    const dinosaur = getDinosaurById(dinosaurId);
15    if (!dinosaur) {
16      console.error(`Nie znaleziono dinozaura o ID: ${dinosaurId}`);
17      return false;
18    }
19
20    // Tworzymy nowy monitor i dodajemy do aktywnych
21    const monitor = {
22      dinosaurId,
23      species: dinosaur.species,
24      startTime: Date.now(),
25      lastUpdate: Date.now(),
26      status: 'active',
27      readings: [],
28      checkInterval: getSpeciesCheckInterval(dinosaur.species)
29    };
30
31    this.activeMonitors.set(dinosaurId, monitor);
32
33    // Uruchamiamy interwał do regularnego sprawdzania
34    const intervalId = setInterval(() => {
35      this.checkDinosaurStatus(dinosaurId);
36    }, monitor.checkInterval);
37
38    monitor.intervalId = intervalId;
39
40    console.log(`Rozpoczęto monitoring dinozaura ${dinosaurId} (${dinosaur.species})`);
41    return true;
42  }
43
44  checkDinosaurStatus(dinosaurId) {
45    const monitor = this.activeMonitors.get(dinosaurId);
46
47    if (!monitor) {
48      console.error(`Brak aktywnego monitoringu dla dinozaura ${dinosaurId}`);
49      return null;
50    }
51
52    try {
53      // Pobieranie aktualnych danych
54      const dinosaur = getDinosaurById(dinosaurId);
55      const location = getLastKnownLocation(dinosaurId);
56      const vitalSigns = getVitalSigns(dinosaurId);
57      const behavior = analyzeBehavior(dinosaurId, location);
58
59      // BŁĄD: Nieprawidłowe obliczenie statusu zdrowia
60      const healthStatus = calculateHealthStatus(vitalSigns);
61
62      // BŁĄD: Nie sprawdzamy czy location istnieje!
63      const inContainment = isInContainmentZone(location.coordinates);
64
65      const reading = {
66        timestamp: Date.now(),
67        location,
68        vitalSigns,
69        behavior,
70        healthStatus,
71        inContainment
72      };
73
74      // Aktualizujemy monitor
75      monitor.readings.push(reading);
76      monitor.lastUpdate = reading.timestamp;
77      monitor.locationStatus = inContainment ? 'contained' : 'breach';
78      monitor.healthStatus = healthStatus;
79
80      // Sprawdzamy poziom zagrożenia
81      this.assessThreatLevel();
82
83      return reading;
84    } catch (error) {
85      console.error(`Błąd sprawdzania statusu dinozaura ${dinosaurId}:`, error);
86      monitor.lastError = {
87        timestamp: Date.now(),
88        message: error.message
89      };
90
91      return null;
92    }
93  }
94
95  stopMonitoring(dinosaurId) {
96    const monitor = this.activeMonitors.get(dinosaurId);
97
98    if (!monitor) {
99      console.warn(`Brak aktywnego monitoringu dla dinozaura ${dinosaurId}`);
100      return false;
101    }
102
103    // Zatrzymujemy interwał
104    clearInterval(monitor.intervalId);
105
106    // Usuwamy z aktywnych monitorów
107    this.activeMonitors.delete(dinosaurId);
108
109    console.log(`Zatrzymano monitoring dinozaura ${dinosaurId}`);
110    return true;
111  }
112
113  assessThreatLevel() {
114    // Logika oceny poziomu zagrożenia na podstawie wszystkich monitorów
115    let highestThreat = 'normal';
116
117    for (const [id, monitor] of this.activeMonitors.entries()) {
118      if (!monitor.locationStatus || monitor.locationStatus === 'breach') {
119        highestThreat = 'critical';
120        break;
121      }
122
123      if (monitor.healthStatus === 'agitated' && highestThreat !== 'critical') {
124        highestThreat = 'elevated';
125      }
126    }
127
128    // Aktualizujemy poziom alertu
129    if (highestThreat !== this.alertLevel) {
130      console.log(`Zmiana poziomu alertu: ${this.alertLevel} -> ${highestThreat}`);
131      this.alertLevel = highestThreat;
132
133      // Powiadomienie o zmianie poziomu alertu
134      notifyAlertLevelChange(this.alertLevel);
135    }
136
137    return this.alertLevel;
138  }
139}

Krok 1: Zidentyfikowanie problemu

Podczas testowania systemu monitorowania w Parku Jurajskim, odkrywamy, że system nieoczekiwanie zgłasza poziom alertu 'critical' dla niektórych dinozaurów, mimo że są one bezpiecznie w swoich wybiegach.

Krok 2: Ustawienie breakpointów

Ustawiamy breakpoint w metodzie checkDinosaurStatus, aby zbadać, co się dzieje:

1checkDinosaurStatus(dinosaurId) {
2  const monitor = this.activeMonitors.get(dinosaurId);
3
4  if (!monitor) {
5    console.error(`Brak aktywnego monitoringu dla dinozaura ${dinosaurId}`);
6    return null;
7  }
8
9  debugger; // Ustawiamy breakpoint tutaj, aby zbadać wszystkie zmienne
10
11  try {
12    // ...reszta kodu
13  }
14}

Krok 3: Krokowe wykonanie i analiza

  1. Wykonujemy kod krok po kroku używając "Step Into" i "Step Over"
  2. Dochodzimy do linii, gdzie sprawdzany jest status lokalizacji:
    1// BŁĄD: Nie sprawdzamy czy location istnieje!
    2const inContainment = isInContainmentZone(location.coordinates);
  3. Sprawdzamy wartość zmiennej location w panelu Scope i widzimy, że jest null
  4. Zidentyfikowany problem: Funkcja getLastKnownLocation czasem zwraca null, gdy nie może znaleźć lokalizacji, a kod nie sprawdza tego przypadku przed próbą dostępu do location.coordinates

Krok 4: Poprawa błędu

Modyfikujemy kod, aby obsługiwał przypadek, gdy location jest null:

1// Bezpieczniejszy kod z weryfikacją
2const location = getLastKnownLocation(dinosaurId);
3const inContainment = location && location.coordinates
4  ? isInContainmentZone(location.coordinates)
5  : false; // Jeśli nie znamy lokalizacji, zakładamy najgorszy scenariusz

Krok 5: Weryfikacja rozwiązania

Po naprawieniu błędu, usuwamy breakpoint i testujemy system ponownie. Tym razem działa poprawnie, nie zgłaszając fałszywych alarmów.

Dobre praktyki debugowania

  1. Używaj znaczących nazw zmiennych - ułatwia to identyfikację zmiennych podczas debugowania
  2. Dodawaj dokumentację do funkcji - komentarze JSDoc pomagają zrozumieć cel funkcji i oczekiwane wartości
  3. Strukturyzuj kod logicznie - przejrzysty kod jest łatwiejszy do debugowania
  4. Używaj defensywnego programowania - sprawdzaj dane wejściowe i obsługuj przypadki brzegowe
  5. Twórz małe, dedykowane funkcje - łatwiej debugować funkcje o pojedynczej odpowiedzialności
  6. Używaj try-catch dla potencjalnie problematycznych części kodu - pomaga to zlokalizować źródło błędów
  7. Unikaj modyfikacji zmiennych debugowanych - może to zmienić zachowanie programu

Podsumowanie

Breakpointy i krokowe wykonanie kodu to nieocenione narzędzia w arsenale każdego programisty pracującego przy systemach Parku Jurajskiego. Pozwalają na:

  • Zatrzymanie wykonania kodu w kluczowych momentach
  • Zbadanie stanu aplikacji, wartości zmiennych i kontekstu wykonania
  • Wykonanie kodu krok po kroku, aby zrozumieć jego przepływ
  • Identyfikację źródła błędów, zwłaszcza w złożonych systemach
  • Weryfikację poprawności działania funkcji i modułów

Pamiętaj, że w Parku Jurajskim, umiejętność szybkiego znalezienia i naprawienia błędów może być kwestią życia lub śmierci - zarówno kodu, jak i (w prawdziwym parku) odwiedzających!

Kod do tej lekcji: main.js
1// Circular Dependencies - Park Jurajski
2// Obsługa zależności cyklicznych w modułach
3
4console.log("Circular Dependencies (Zależności Cykliczne)");
5console.log("Jak radzić sobie z wzajemnymi zależnościami modułów\n");
6
7// ===========================================
8// PROBLEM: Zależności Cykliczne
9// ===========================================
10
11// === dinosaur-manager.js ===
12class DinosaurManager {
13  constructor() {
14    this.dinosaurs = new Map();
15  }
16
17  addDinosaur(dinosaur) {
18    this.dinosaurs.set(dinosaur.id, dinosaur);
19    // Problem: potrzebujemy SecuritySystem, ale on również potrzebuje DinosaurManager
20    console.log(`Dodano dinozaura: ${dinosaur.name}`);
21  }
22
23  getDinosaur(id) {
24    return this.dinosaurs.get(id);
25  }
26
27  getAllDinosaurs() {
28    return Array.from(this.dinosaurs.values());
29  }
30}
31
32// === security-system.js ===
33class SecuritySystem {
34  constructor(dinosaurManager) {
35    this.dinosaurManager = dinosaurManager;
36    this.securityLevel = 'GREEN';
37  }
38
39  checkSecurity() {
40    const dinosaurs = this.dinosaurManager.getAllDinosaurs();
41    const dangerousDinos = dinosaurs.filter(d => d.dangerLevel > 7);
42
43    if (dangerousDinos.length > 2) {
44      this.securityLevel = 'RED';
45    } else if (dangerousDinos.length > 0) {
46      this.securityLevel = 'YELLOW';
47    } else {
48      this.securityLevel = 'GREEN';
49    }
50
51    console.log(`Poziom bezpieczeństwa: ${this.securityLevel}`);
52    return this.securityLevel;
53  }
54
55  alertForDinosaur(dinosaur) {
56    if (dinosaur.dangerLevel > 7) {
57      console.log(`ALERT: ${dinosaur.name} jest niebezpieczny!`);
58    }
59  }
60}
61
62// === dinosaur.js ===
63class Dinosaur {
64  constructor(name, species, dangerLevel, securitySystem) {
65    this.id = Math.random().toString(36).substr(2, 9);
66    this.name = name;
67    this.species = species;
68    this.dangerLevel = dangerLevel;
69    this.securitySystem = securitySystem;
70  }
71
72  roar() {
73    console.log(`${this.name}: ROAAR!`);
74    // Problem: potrzebujemy SecuritySystem, który potrzebuje DinosaurManager
75    if (this.securitySystem) {
76      this.securitySystem.alertForDinosaur(this);
77    }
78  }
79}
80
81// ===========================================
82// ROZWIĄZANIE 1: Dependency Injection
83// ===========================================
84
85console.log("=== ROZWIĄZANIE 1: Dependency Injection ===\n");
86
87const manager = new DinosaurManager();
88const security = new SecuritySystem(manager);
89
90const trex = new Dinosaur('Rexy', 'T-Rex', 9, security);
91const raptor = new Dinosaur('Blue', 'Velociraptor', 7, security);
92const brachi = new Dinosaur('Gentle Giant', 'Brachiosaurus', 2, security);
93
94manager.addDinosaur(trex);
95manager.addDinosaur(raptor);
96manager.addDinosaur(brachi);
97
98console.log("\nSprawdzanie bezpieczeństwa...");
99security.checkSecurity();
100
101console.log("\nTest dźwięków:");
102trex.roar();
103raptor.roar();
104brachi.roar();
105
106// ===========================================
107// ROZWIĄZANIE 2: Event System
108// ===========================================
109
110console.log("\n=== ROZWIĄZANIE 2: Event System ===\n");
111
112class EventBus {
113  constructor() {
114    this.listeners = {};
115  }
116
117  on(event, callback) {
118    if (!this.listeners[event]) {
119      this.listeners[event] = [];
120    }
121    this.listeners[event].push(callback);
122  }
123
124  emit(event, data) {
125    if (this.listeners[event]) {
126      this.listeners[event].forEach(callback => callback(data));
127    }
128  }
129}
130
131const eventBus = new EventBus();
132
133class DinosaurManagerV2 {
134  constructor(eventBus) {
135    this.eventBus = eventBus;
136    this.dinosaurs = new Map();
137
138    // Nasłuchuj na wydarzenia
139    this.eventBus.on('dinosaur:added', (dino) => {
140      console.log(`Event: Dodano ${dino.name}`);
141    });
142  }
143
144  addDinosaur(dinosaur) {
145    this.dinosaurs.set(dinosaur.id, dinosaur);
146    this.eventBus.emit('dinosaur:added', dinosaur);
147  }
148
149  getAllDinosaurs() {
150    return Array.from(this.dinosaurs.values());
151  }
152}
153
154class SecuritySystemV2 {
155  constructor(eventBus) {
156    this.eventBus = eventBus;
157    this.securityLevel = 'GREEN';
158
159    // Nasłuchuj na wydarzenia
160    this.eventBus.on('dinosaur:added', (dino) => {
161      this.evaluateThreat(dino);
162    });
163  }
164
165  evaluateThreat(dinosaur) {
166    if (dinosaur.dangerLevel > 7) {
167      console.log(`Event Security: ${dinosaur.name} wymaga monitorowania`);
168      this.securityLevel = 'YELLOW';
169    }
170  }
171}
172
173const eventBusInstance = new EventBus();
174const managerV2 = new DinosaurManagerV2(eventBusInstance);
175const securityV2 = new SecuritySystemV2(eventBusInstance);
176
177console.log("Dodawanie dinozaurów z event system:");
178managerV2.addDinosaur({ id: '1', name: 'Rex Jr', dangerLevel: 8 });
179managerV2.addDinosaur({ id: '2', name: 'Herbivore', dangerLevel: 2 });
180
181// ===========================================
182// ROZWIĄZANIE 3: Lazy Loading / Late Binding
183// ===========================================
184
185console.log("\n=== ROZWIĄZANIE 3: Lazy Loading ===\n");
186
187class DinosaurManagerV3 {
188  constructor() {
189    this.dinosaurs = new Map();
190    this._securitySystem = null;
191  }
192
193  setSecuritySystem(security) {
194    this._securitySystem = security;
195  }
196
197  addDinosaur(dinosaur) {
198    this.dinosaurs.set(dinosaur.id, dinosaur);
199    console.log(`Lazy: Dodano ${dinosaur.name}`);
200
201    // Lazy evaluation - security jest dostępny dopiero teraz
202    if (this._securitySystem) {
203      this._securitySystem.checkSecurity();
204    }
205  }
206
207  getAllDinosaurs() {
208    return Array.from(this.dinosaurs.values());
209  }
210}
211
212class SecuritySystemV3 {
213  constructor() {
214    this._dinosaurManager = null;
215  }
216
217  setDinosaurManager(manager) {
218    this._dinosaurManager = manager;
219  }
220
221  checkSecurity() {
222    if (!this._dinosaurManager) {
223      console.log("DinosaurManager nie został jeszcze przypisany");
224      return;
225    }
226
227    const dinosaurs = this._dinosaurManager.getAllDinosaurs();
228    console.log(`Sprawdzam bezpieczeństwo dla ${dinosaurs.length} dinozaurów`);
229  }
230}
231
232const managerV3 = new DinosaurManagerV3();
233const securityV3 = new SecuritySystemV3();
234
235// Ustaw referencje po utworzeniu obu obiektów
236managerV3.setSecuritySystem(securityV3);
237securityV3.setDinosaurManager(managerV3);
238
239managerV3.addDinosaur({ id: '1', name: 'Test Dino', dangerLevel: 5 });
240
241console.log("\n=== NAJLEPSZE PRAKTYKI ===\n");
242console.log("Dependency Injection:");
243console.log("   - Przekazuj zależności przez konstruktor/parametry");
244console.log("   - Testowalne i przewidywalne\n");
245
246console.log("Event System:");
247console.log("   - Luźne powiązanie między modułami");
248console.log("   - Skalowalne dla wielu subskrybentów\n");
249
250console.log("Lazy Loading:");
251console.log("   - Opóźnione wiązanie zależności");
252console.log("   - Dobre dla opcjonalnych zależności\n");
253
254console.log("UNIKAJ:");
255console.log("   - Bezpośrednich require/import tworzących cykle");
256console.log("   - Globalnych zmiennych jako rozwiązania");
257console.log("   - Tight coupling między modułami\n");
258
259console.log("Demonstracja zakończona!");

Widzisz błąd w tej lekcji?

Zadania praktyczne w grze

  • Układanie w pionie

    Uporządkuj etapy debugowania kodu od początku do końca:

Przydatne artykuły