Kurs JavaScript i TypeScript · Moduł 5: Zaawansowany JavaScript
Najlepsze praktyki
W tej lekcji3
Tym razem Dr. Ian Malcolm wraz z całym zespołem Parku Jurajskiego przygotowali dla Ciebie kluczowy briefing. Po kilku incydentach w parku, w tym słynnej awarii systemu i ucieczce dinozaurów, zespół opracował zestaw najlepszych praktyk programistycznych, które mogłyby zapobiec podobnym katastrofom w przyszłości.
Zasady, które mogłyby uratować Park Jurajski
1. Waliduj wszystkie dane wejściowe
Problem w parku: System nie sprawdzał poprawnie danych o temperaturze inkubatorów jaj dinozaurów, co doprowadziło do niekontrolowanego rozmnażania się welociraptorów.
Rozwiązanie:
1// Źle:
2function ustawTemperaturęInkubatora(temperatura) {
3 inkubator.temperatura = temperatura;
4 // Co jeśli temperatura będzie zbyt wysoka? Lub to będzie string zamiast liczby?
5}
6
7// Dobrze:
8function ustawTemperaturęInkubatora(temperatura) {
9 // Sprawdzamy czy temperatura jest liczbą
10 if (typeof temperatura !== 'number') {
11 throw new Error('Temperatura musi być liczbą');
12 }
13
14 // Sprawdzamy czy temperatura mieści się w bezpiecznym zakresie
15 if (temperatura < 20 || temperatura > 40) {
16 throw new Error('Temperatura poza bezpiecznym zakresem (20-40°C)');
17 }
18
19 inkubator.temperatura = temperatura;
20}2. Używaj testów automatycznych
Problem w parku: Nikt nie przetestował systemu ogrodzenia elektrycznego w warunkach ekstremalnych, takich jak tropikalna burza.
Rozwiązanie:
1// Zestaw testów dla systemu ogrodzenia
2describe('System ogrodzenia', () => {
3 test('powinien działać przy normalnym zasilaniu', () => {
4 const ogrodzenie = new SystemOgrodzenia();
5 ogrodzenie.włącz();
6 expect(ogrodzenie.czyAktywne()).toBe(true);
7 });
8
9 test('powinien przełączyć się na zasilanie awaryjne podczas burzy', () => {
10 const ogrodzenie = new SystemOgrodzenia();
11 ogrodzenie.włącz();
12 ogrodzenie.symulujPrzerwęWDostawieEnergii();
13 expect(ogrodzenie.czyAktywne()).toBe(true);
14 expect(ogrodzenie.źródłoZasilania).toBe('awaryjne');
15 });
16
17 test('powinien wysłać alert gdy baterie awaryjne są poniżej 20%', () => {
18 const ogrodzenie = new SystemOgrodzenia();
19 const alertMock = jest.fn();
20 ogrodzenie.onNiskiPoziomBaterii = alertMock;
21
22 ogrodzenie.ustawPoziomBaterii(15);
23
24 expect(alertMock).toHaveBeenCalled();
25 });
26});3. Stosuj zasadę pojedynczej odpowiedzialności
Problem w parku: Jeden system sterował zarówno drzwiami, jak i ogrodzeniami, kamerami oraz systemem bezpieczeństwa. Awaria jednego komponentu doprowadziła do kaskadowej awarii całego systemu.
Rozwiązanie:
1// Źle:
2class SystemParku {
3 kontrolujDrzwi() { /* ... */ }
4 zarządzajOgrodzeniem() { /* ... */ }
5 monitorujKamery() { /* ... */ }
6 sterujKlimatyzacją() { /* ... */ }
7 // Ten obiekt robi zbyt wiele!
8}
9
10// Dobrze:
11class SystemDrzwi {
12 otwórz(id) { /* ... */ }
13 zamknij(id) { /* ... */ }
14 sprawdźStan(id) { /* ... */ }
15}
16
17class SystemOgrodzenia {
18 włącz() { /* ... */ }
19 wyłącz() { /* ... */ }
20 sprawdźNapięcie() { /* ... */ }
21}
22
23class SystemMonitoringu {
24 włączKamerę(id) { /* ... */ }
25 wyłączKamerę(id) { /* ... */ }
26 nagrywaj(id) { /* ... */ }
27}
28
29class CentralnySterownik {
30 constructor(systemDrzwi, systemOgrodzenia, systemMonitoringu) {
31 this.systemDrzwi = systemDrzwi;
32 this.systemOgrodzenia = systemOgrodzenia;
33 this.systemMonitoringu = systemMonitoringu;
34 }
35
36 uruchomProcedurębezpieczeństwa() {
37 // Teraz koordynuje niezależne systemy
38 this.systemDrzwi.zamknij('wszystkie');
39 this.systemOgrodzenia.włącz();
40 this.systemMonitoringu.nagrywaj('wszystkie');
41 }
42}4. Obsługuj błędy i wyjątki
Problem w parku: System transportu jaj nie posiadał obsługi błędów, co doprowadziło do utraty cennego materiału genetycznego, gdy jeden z czujników przestał działać.
Rozwiązanie:
1// Źle:
2function przenieśJajo(idJaja, docelowaPozycja) {
3 const jajo = bazaDanych.pobierzJajo(idJaja);
4 ramięRobotyczne.przenieś(jajo, docelowaPozycja);
5 bazaDanych.aktualizujPozycję(idJaja, docelowaPozycja);
6 // Co jeśli którakolwiek z tych operacji się nie powiedzie?
7}
8
9// Dobrze:
10async function przenieśJajo(idJaja, docelowaPozycja) {
11 try {
12 // Pobieramy jajo z bazy
13 const jajo = await bazaDanych.pobierzJajo(idJaja);
14 if (!jajo) {
15 throw new Error(`Jajo o ID ${idJaja} nie zostało znalezione`);
16 }
17
18 // Przenosimy jajo za pomocą ramienia robotycznego
19 try {
20 await ramięRobotyczne.przenieś(jajo, docelowaPozycja);
21 } catch (błąd) {
22 console.error('Błąd podczas przenoszenia jaja:', błąd);
23 wyslijAlert('Błąd ramienia robotycznego', błąd.message);
24 return false;
25 }
26
27 // Aktualizujemy pozycję w bazie danych
28 await bazaDanych.aktualizujPozycję(idJaja, docelowaPozycja);
29
30 // Logujemy udaną operację
31 logOperacji.dodaj(`Jajo ${idJaja} przeniesione do ${docelowaPozycja}`);
32
33 return true;
34 } catch (błąd) {
35 console.error('Krytyczny błąd podczas przenoszenia jaja:', błąd);
36 wyslijAlert('Krytyczny błąd', błąd.message);
37 uruchomProcedurębezpieczeństwa();
38 return false;
39 }
40}5. Dokumentuj swój kod
Problem w parku: Po odejściu głównego inżyniera, nikt nie wiedział dokładnie jak działa system kontroli dostępu.
Rozwiązanie:
1/**
2 * Weryfikuje poziom dostępu pracownika do stref parku.
3 *
4 * @param {Object} pracownik - Obiekt zawierający dane pracownika
5 * @param {string} pracownik.id - Unikalny identyfikator pracownika
6 * @param {string} pracownik.imię - Imię pracownika
7 * @param {string} pracownik.nazwisko - Nazwisko pracownika
8 * @param {number} pracownik.poziomDostępu - Poziom dostępu pracownika (1-5)
9 * @param {string} strefa - Identyfikator strefy, do której pracownik próbuje uzyskać dostęp
10 * @returns {boolean} - Zwraca true, jeśli pracownik ma dostęp do strefy, false w przeciwnym razie
11 *
12 * @example
13 * // Sprawdź czy Dr. Wu ma dostęp do laboratorium
14 * const drWu = { id: '001', imię: 'Henry', nazwisko: 'Wu', poziomDostępu: 5 };
15 * const dostęp = sprawdźDostęp(drWu, 'laboratorium');
16 * console.log(dostęp); // true
17 */
18function sprawdźDostęp(pracownik, strefa) {
19 // Mapa wymaganych poziomów dostępu dla różnych stref
20 const wymaganyPoziom = {
21 'publiczna': 1,
22 'administracja': 2,
23 'zwierzęta roślinożerne': 3,
24 'drapieżniki': 4,
25 'laboratorium': 5
26 };
27
28 // Sprawdź czy strefa istnieje
29 if (!wymaganyPoziom.hasOwnProperty(strefa)) {
30 console.warn(`Nieznana strefa: ${strefa}`);
31 return false;
32 }
33
34 // Porównaj poziom dostępu pracownika z wymaganym dla strefy
35 return pracownik.poziomDostępu >= wymaganyPoziom[strefa];
36}6. Unikaj globalnego stanu
Problem w parku: System kontroli klimatu używał globalnych zmiennych do przechowywania ustawień, co pozwoliło błędowi w module sterowania wentylacją wpłynąć na cały system klimatyzacji.
Rozwiązanie:
1// Źle:
2let globalnaTemperatura = 22;
3let globalnaWilgotność = 70;
4
5function ustawTemperaturęWDżungli() {
6 // Modyfikuje zmienne globalne
7 globalnaTemperatura = 28;
8 globalnaWilgotność = 90;
9}
10
11function ustawTemperaturęWLaboratorium() {
12 // Również modyfikuje te same zmienne globalne!
13 globalnaTemperatura = 20;
14 globalnaWilgotność = 50;
15}
16
17// Co się stanie, jeśli obie funkcje zostaną wywołane blisko siebie w czasie?
18
19// Dobrze:
20class KontrolerKlimatu {
21 constructor() {
22 this.strefy = new Map();
23 }
24
25 ustawParametryStefy(idStrefy, temperatura, wilgotność) {
26 this.strefy.set(idStrefy, { temperatura, wilgotność });
27 }
28
29 pobierzParametryStefy(idStrefy) {
30 return this.strefy.get(idStrefy);
31 }
32}
33
34const kontrolerKlimatu = new KontrolerKlimatu();
35
36kontrolerKlimatu.ustawParametryStefy('dżungla', 28, 90);
37kontrolerKlimatu.ustawParametryStefy('laboratorium', 20, 50);
38
39// Teraz każda strefa ma własne niezależne ustawienia7. Stosuj DRY (Don't Repeat Yourself)
Problem w parku: Kod do sprawdzania stanu zagrożenia był powielony w kilku systemach, więc poprawka błędu w jednym systemie nie została wprowadzona w pozostałych.
Rozwiązanie:
1// Źle:
2function sprawdźZagrożenieWSystemieDrzwi() {
3 if (czujniki.ruch > 10 && czujniki.dźwięk > 80 && !jestDzień()) {
4 ogłośAlarm('Podejrzana aktywność przy drzwiach!');
5 }
6}
7
8function sprawdźZagrożenieWSystemieOgrodzenia() {
9 // Ten sam logika jest powtórzona!
10 if (czujniki.ruch > 10 && czujniki.dźwięk > 80 && !jestDzień()) {
11 ogłośAlarm('Podejrzana aktywność przy ogrodzeniu!');
12 }
13}
14
15// Dobrze:
16function wykryjPodejrzanąAktywność() {
17 return czujniki.ruch > 10 && czujniki.dźwięk > 80 && !jestDzień();
18}
19
20function sprawdźZagrożenieWSystemieDrzwi() {
21 if (wykryjPodejrzanąAktywność()) {
22 ogłośAlarm('Podejrzana aktywność przy drzwiach!');
23 }
24}
25
26function sprawdźZagrożenieWSystemieOgrodzenia() {
27 if (wykryjPodejrzanąAktywność()) {
28 ogłośAlarm('Podejrzana aktywność przy ogrodzeniu!');
29 }
30}8. Pisz kod dla ludzi, nie dla komputerów
Problem w parku: Kod genetyczny napisany przez Dr. Wu był tak zagmatwany, że nikt inny nie rozumiał jak dokładnie tworzono hybrydowe dinozaury.
Rozwiązanie:
1// Źle:
2function ms(d,g,s) {
3 const r = [];
4 for(let i=0;i<d.length;i++) {
5 if(d[i].g === g && d[i].t > s) {
6 r.push(d[i]);
7 }
8 }
9 return r;
10}
11
12// Dobrze:
13/**
14 * Filtruje dinozaury według gatunku i minimalnego poziomu siły
15 */
16function filtrujDinozauryPoGatunkuISile(dinozaury, gatunek, minimalnaSila) {
17 const wyfiltrowane = [];
18
19 for (let i = 0; i < dinozaury.length; i++) {
20 const dinozaur = dinozaury[i];
21
22 if (dinozaur.gatunek === gatunek && dinozaur.siła > minimalnaSila) {
23 wyfiltrowane.push(dinozaur);
24 }
25 }
26
27 return wyfiltrowane;
28}
29
30// Lub jeszcze lepiej, używając nowoczesnego JavaScript:
31function filtrujDinozauryPoGatunkuISile(dinozaury, gatunek, minimalnaSila) {
32 return dinozaury.filter(dinozaur =>
33 dinozaur.gatunek === gatunek && dinozaur.siła > minimalnaSila
34 );
35}9. Używaj kontroli wersji
Problem w parku: Zespół IT nie używał systemu kontroli wersji, więc po wprowadzeniu nowej funkcjonalności, która okazała się zawierać błędy, nie można było łatwo przywrócić poprzedniej działającej wersji.
Rozwiązanie:
1# Inicjalizacja repozytorium Git
2git init
3
4# Dodanie plików do repozytorium
5git add .
6
7# Pierwszy commit
8git commit -m "Początkowa wersja systemu Parku Jurajskiego"
9
10# Tworzenie nowej gałęzi na potrzeby rozwoju nowej funkcjonalności
11git checkout -b nowy-system-karmienia
12
13# Praca nad funkcjonalnością...
14
15# Zapisanie zmian
16git add zmodyfikowane-pliki
17git commit -m "Dodano automatyczny system karmienia dla drapieżników"
18
19# Łączenie zmian z główną gałęzią po testach
20git checkout main
21git merge nowy-system-karmienia
22
23# W przypadku problemów, można szybko wrócić do poprzedniej wersji
24git revert HEAD10. Stosuj przeglądowanie kodu (code review)
Problem w parku: Dennis Nedry wprowadził tylne drzwi do systemu bezpieczeństwa, których nikt nie zauważył, ponieważ nie było procesu przeglądania kodu.
Podejście w zespole:
- Każda zmiana musi być przeglądnięta przez co najmniej jednego innego programistę
- Automatyczne testy muszą przejść przed scaleniem kodu
- Stosowanie listy kontrolnej podczas przeglądania:
- Czy kod jest zgodny ze standardami?
- Czy potencjalne problemy bezpieczeństwa zostały zaadresowane?
- Czy dokumentacja została zaktualizowana?
- Czy testy pokrywają nowy kod?
Koszt nieprzestrzegania najlepszych praktyk
Jak już wiemy z historii Parku Jurajskiego, nieprzestrzeganie najlepszych praktyk programistycznych może prowadzić do katastrofalnych konsekwencji:
- Utrata danych: Bezcenny materiał genetyczny dinozaurów utracony z powodu błędów w systemie
- Zagrożenie bezpieczeństwa: Awarie systemu prowadzące do ucieczki niebezpiecznych stworzeń
- Przerwy w działaniu: Zamknięcie parku z powodu problemów technicznych
- Trudności w rozwoju: Niemożność szybkiego wprowadzania nowych funkcji
- Problemy z utrzymaniem: Trudny do zrozumienia i naprawy kod
Ćwiczenie
Wyobraź sobie, że jesteś głównym programistą w Parku Jurajskim. Zidentyfikuj wszystkie potencjalne problemy w poniższym kodzie i przepisz go zgodnie z najlepszymi praktykami:
1// System monitorowania dinozaurów w Parku Jurajskim
2var dinos = [];
3var alarmy = 0;
4
5function dodaj(name, type, area, dangerLvl) {
6 dinos.push({n: name, t: type, a: area, d: dangerLvl, h: 100});
7 console.log('dodano: ' + name);
8}
9
10function check() {
11 for (var i=0; i<dinos.length; i++) {
12 if (dinos[i].h < 50) {
13 console.log('UWAGA! Dinozaur ' + dinos[i].n + ' ma niski poziom zdrowia!');
14 alarmy = alarmy + 1;
15 }
16
17 if (dinos[i].a != dinos[i].lockArea && dinos[i].d > 3) {
18 console.log('NIEBEZPIECZEŃSTWO! Drapieżnik poza strefą!');
19 alarmy = alarmy + 1;
20 }
21 }
22
23 if (alarmy > 0) alert('Mamy problemy...');
24}
25
26// Przykład użycia
27dodaj('Rex', 'T-Rex', 'B5', 5);
28dodaj('Tri', 'Triceratops', 'A2', 2);
29dinos[0].h = 30;
30dinos[0].a = 'X3'; // Poza przypisaną strefą!
31check();Pamiętaj, w świecie programowania, tak jak w Parku Jurajskim - życie zawsze znajdzie sposób... na ujawnienie nieprzemyślanych rozwiązań! Dlatego stosuj najlepsze praktyki, aby twój kod był bezpieczny, utrzymywalny i efektywny.
Kod do tej lekcji: main.js
1// Dobre praktyki z modułami - Park Jurajski
2console.log("Dobre Praktyki z Modułami\n");
3
4// ===========================================
5// 1. JASNA STRUKTURA FOLDERÓW
6// ===========================================
7
8console.log("=== 1. Struktura Projektu ===\n");
9console.log(`jurassic-park/
10├── src/
11│ ├── models/
12│ │ ├── Dinosaur.js
13│ │ ├── Enclosure.js
14│ │ └── index.js
15│ ├── services/
16│ │ ├── DinosaurService.js
17│ │ ├── SecurityService.js
18│ │ └── index.js
19│ ├── utils/
20│ │ ├── validators.js
21│ │ ├── formatters.js
22│ │ └── index.js
23│ ├── config/
24│ │ └── constants.js
25│ └── index.js
26└── package.json`);
27
28// ===========================================
29// 2. JEDNĄ ODPOWIEDZIALNOŚĆ NA MODUŁ
30// ===========================================
31
32console.log("\n=== 2. Single Responsibility ===\n");
33
34// DOBRZE - każdy moduł robi jedno
35const DinosaurValidator = {
36 validate(data) {
37 return data.name && data.species;
38 }
39};
40
41const DinosaurFormatter = {
42 format(dinosaur) {
43 return `${dinosaur.name} - ${dinosaur.species}`;
44 }
45};
46
47// ŹLE - jeden moduł robi za dużo
48const DinosaurEverything = {
49 validate() {},
50 format() {},
51 save() {},
52 load() {},
53 update() {},
54 delete() {}
55};
56
57console.log("Moduły z jedną odpowiedzialnością");
58console.log("Unikaj 'god objects'");
59
60// ===========================================
61// 3. NAZEWNICTWO
62// ===========================================
63
64console.log("\n=== 3. Konwencje Nazewnictwa ===\n");
65
66console.log("DOBRZE:");
67console.log(" DinosaurService.js - klasa/serwis");
68console.log(" constants.js - stałe");
69console.log(" utils.js - funkcje użytkowe");
70console.log(" index.js - re-export");
71
72console.log("\nŹLE:");
73console.log(" dino.js - niejasne");
74console.log(" stuff.js - zbyt ogólne");
75console.log(" temp.js - tymczasowe pliki");
76
77// ===========================================
78// 4. DEPENDENCY INJECTION
79// ===========================================
80
81console.log("\n=== 4. Dependency Injection ===\n");
82
83// DOBRZE - dependencies jako parametry
84class ParkManager {
85 constructor(dinosaurService, securityService) {
86 this.dinosaurService = dinosaurService;
87 this.securityService = securityService;
88 }
89}
90
91// ŹLE - hard-coded dependencies
92class BadParkManager {
93 constructor() {
94 this.dinosaurService = new DinosaurService();
95 this.securityService = new SecurityService();
96 }
97}
98
99console.log("Przekazuj zależności");
100console.log("Nie twórz zależności wewnątrz");
101
102// ===========================================
103// 5. EKSPORTUJ TYLKO CO POTRZEBNE
104// ===========================================
105
106console.log("\n=== 5. Minimalna Powierzchnia API ===\n");
107
108// DOBRZE - eksportuj tylko publiczne API
109const DinosaurModule = {
110 // Publiczne
111 createDinosaur(name) {
112 return _createInternal(name);
113 },
114
115 getDinosaur(id) {
116 return _findById(id);
117 }
118};
119
120// Prywatne funkcje (nie eksportowane)
121function _createInternal(name) {
122 return { name, id: Math.random() };
123}
124
125function _findById(id) {
126 return { id };
127}
128
129console.log("Minimalna publiczna powierzchnia");
130console.log("Nie eksportuj internal helpers");
131
132// ===========================================
133// 6. DOKUMENTACJA
134// ===========================================
135
136console.log("\n=== 6. Dokumentacja Modułów ===\n");
137
138/**
139 * Dinosaur Service
140 * Handles all dinosaur-related operations
141 *
142 * @module DinosaurService
143 * @exports {Object} Service object with public methods
144 *
145 * @example
146 * import DinosaurService from './DinosaurService';
147 * const dino = DinosaurService.create('Rex');
148 */
149const DinosaurService = {
150 /**
151 * Creates a new dinosaur
152 * @param {string} name - Dinosaur name
153 * @returns {Object} Dinosaur object
154 */
155 create(name) {
156 return { name };
157 }
158};
159
160console.log("JSDoc dla publicznych API");
161console.log("README dla większych modułów");
162
163console.log("\n=== PODSUMOWANIE PRAKTYK ===\n");
164console.log("1. Jasna struktura folderów");
165console.log("2. Jedna odpowiedzialność na moduł");
166console.log("3. Jasne nazwy plików");
167console.log("4. Dependency Injection");
168console.log("5. Minimalne eksporty");
169console.log("6. Dokumentacja");
170console.log("7. Testy dla każdego modułu");
171console.log("8. Unikaj circular dependencies");
172
173console.log("\nCzyste moduły = lepszy kod!");Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Co oznacza zasada pojedynczej odpowiedzialności (Single Responsibility Principle)?
2. Dlaczego walidacja danych wejściowych jest kluczowa w programowaniu?
Zadania praktyczne w grze
- Układanie w pionie
Uporządkuj etapy tworzenia niezawodnego kodu od pierwszego do ostatniego: