Kurs JavaScript i TypeScript · Moduł 5: Zaawansowany JavaScript
Breakpointy i krokowe wykonanie kodu
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):
- Otwórz narzędzia deweloperskie (<kbd>F12</kbd> lub <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>I</kbd>)
- Przejdź do zakładki "Sources" (Chrome) lub "Debugger" (Firefox)
- Znajdź i otwórz plik, w którym chcesz ustawić breakpoint
- Kliknij numer linii, w której chcesz zatrzymać wykonanie kodu
- 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
- Ustaw zwykły breakpoint, klikając numer linii
- Kliknij prawym przyciskiem myszy na breakpoint
- Wybierz opcję "Edit breakpoint" lub "Add condition"
- Wprowadź warunek, na przykład:
dinosaurId === 'TRX-01'lubdangerLevel > 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ść
thisw 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:
- Otwórz panel Sources/Debugger
- Znajdź sekcję "Breakpoints" (lub podobną)
- Zaznacz opcję "Pause on exceptions" (Zatrzymaj na wyjątkach)
- 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
- Wykonujemy kod krok po kroku używając "Step Into" i "Step Over"
- Dochodzimy do linii, gdzie sprawdzany jest status lokalizacji:
1// BŁĄD: Nie sprawdzamy czy location istnieje! 2const inContainment = isInContainmentZone(location.coordinates); - Sprawdzamy wartość zmiennej
locationw panelu Scope i widzimy, że jestnull - Zidentyfikowany problem: Funkcja
getLastKnownLocationczasem zwracanull, gdy nie może znaleźć lokalizacji, a kod nie sprawdza tego przypadku przed próbą dostępu dolocation.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 scenariuszKrok 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
- Używaj znaczących nazw zmiennych - ułatwia to identyfikację zmiennych podczas debugowania
- Dodawaj dokumentację do funkcji - komentarze JSDoc pomagają zrozumieć cel funkcji i oczekiwane wartości
- Strukturyzuj kod logicznie - przejrzysty kod jest łatwiejszy do debugowania
- Używaj defensywnego programowania - sprawdzaj dane wejściowe i obsługuj przypadki brzegowe
- Twórz małe, dedykowane funkcje - łatwiej debugować funkcje o pojedynczej odpowiedzialności
- Używaj try-catch dla potencjalnie problematycznych części kodu - pomaga to zlokalizować źródło błędów
- 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: