Po przejściu przez wszystkie najważniejsze techniki JavaScript, nadszedł czas, aby spojrzeć na szerszy obraz - jak organizować kod w większych projektach. Nieodpowiednia struktura kodu może doprowadzić do katastrofy podobnej do tej, z którą mieliśmy do czynienia w Parku Jurajskim - chaos, błędy i trudność w zarządzaniu systemem.
Wyobraź sobie, że całą infrastrukturę Parku Jurajskiego zarządzaną przez jeden ogromny system. Wszystkie zabezpieczenia wybiegów, monitorowanie zdrowia dinozaurów, zarządzanie przepływem zwiedzających, gastronomia, laboratoria genetyczne - wszystko w jednym, monolitycznym programie. Katastrofa czeka tuż za rogiem, prawda?
Dobrze zaprojektowana struktura projektu zapewnia:
Istnieje wiele podejść do organizacji kodu w większych projektach JavaScript, ale przyjrzyjmy się kilku sprawdzonym wzorcom, które sprawdziłyby się w systemie zarządzania Parkiem Jurajskim.
Jedna z popularnych struktur, szczególnie w mniejszych aplikacjach, to grupowanie plików według ich typu:
1jurassic-park-management/
2├── src/
3│ ├── components/ # Komponenty UI
4│ ├── services/ # Serwisy do komunikacji z API
5│ ├── utils/ # Narzędzia pomocnicze
6│ ├── models/ # Definicje typów i interfejsów
7│ ├── hooks/ # Niestandardowe hooki
8│ ├── constants/ # Stałe używane w aplikacji
9│ ├── assets/ # Obrazy, czcionki, itp.
10│ ├── styles/ # Style CSS/SCSS
11│ └── tests/ # Testy
12└── package.jsonW tej strukturze, jeśli chcemy dodać nową funkcjonalność, np. monitorowanie zdrowia dinozaurów, będziemy musieli dodać:
/components/services/models/utilsTo podejście jest proste i zrozumiałe, ale w miarę rozrostu projektu może stać się trudniejsze do zarządzania, ponieważ powiązane ze sobą pliki są rozrzucone po różnych katalogach.
Alternatywne podejście to organizowanie kodu według funkcjonalności biznesowej:
1jurassic-park-management/
2├── src/
3│ ├── common/ # Współdzielone komponenty, style, utils
4│ ├── features/
5│ │ ├── dinosaur-monitoring/ # Wszystko związane z monitorowaniem dinozaurów
6│ │ │ ├── components/
7│ │ │ ├── services/
8│ │ │ ├── hooks/
9│ │ │ ├── utils/
10│ │ │ └── tests/
11│ │ ├── enclosure-security/ # Wszystko związane z zabezpieczeniami wybiegów
12│ │ │ ├── components/
13│ │ │ ├── services/
14│ │ │ └──
15│ │ ├── visitor-management/ # Wszystko związane z zarządzaniem zwiedzającymi
16│ │ │ ├── components/
17│ │ │ ├── services/
18│ │ │ └──
19│ │ └──
20│ ├── core/ # Core functionality
21│ │ ├── api/
22│ │ ├── auth/
23│ │ └── config/
24│ └── app.js
25└── package.jsonW tej strukturze, jeśli pracujemy nad systemem monitorowania dinozaurów, wszystkie potrzebne pliki znajdują się w jednym miejscu. To podejście jest bardziej skalowalne dla dużych projektów, ponieważ funkcjonalności są od siebie odizolowane.
Jeszcze bardziej zaawansowane podejście to organizacja kodu w postaci modułów, które są w pełni enkapsulowane:
1jurassic-park-management/
2├── src/
3│ ├── modules/
4│ │ ├── dinosaur-monitoring/
5│ │ │ ├── components/
6│ │ │ ├── services/
7│ │ │ ├── store/
8│ │ │ ├── utils/
9│ │ │ ├── index.js # Eksportuje publiczne API modułu
10│ │ │ └── README.md # Dokumentacja modułu
11│ │ ├── enclosure-security/
12│ │ │ ├──
13│ │ │ └── index.js
14│ │ └──
15│ ├── shared/ # Współdzielone zasoby
16│ │ ├── components/
17│ │ ├── utils/
18│ │ └── hooks/
19│ ├── core/ # Core functionality
20│ │ ├── api/
21│ │ ├── router/
22│ │ └── store/
23│ └── app.js
24└── package.jsonW tym podejściu każdy moduł jest traktowany jako "mini-aplikacja" z własnym API udostępnianym przez plik
index.js. Inne moduły mogą korzystać tylko z tego, co jest jawnie eksportowane, co zapewnia lepszą enkapsulację i zmniejsza zależności między modułami.Struktura folderów to tylko część zagadnienia. Równie ważne jest zaprojektowanie odpowiedniej architektury aplikacji, która określa, jak różne części kodu współpracują ze sobą.
Klasyczny wzorzec MVC dzieli aplikację na trzy główne komponenty:
W kontekście Parku Jurajskiego:
Flux (i jego implementacja Redux) to architektura oparta na jednokierunkowym przepływie danych, która świetnie sprawdza się w aplikacjach React.
1┌──────────┐ ┌──────────┐ ┌───────────────┐
2│ Action │──────>│ Store │──────>│ Components │
3└──────────┘ └──────────┘ └───────────────┘
4 │
5 │
6 V
7 ┌──────────┐
8 │ Actions │
9 └──────────┘W kontekście Parku Jurajskiego:
Te architektury kładą nacisk na oddzielenie logiki biznesowej od infrastruktury (UI, bazy danych, etc.), co ułatwia testowanie i zmiany infrastruktury bez naruszania core'owej funkcjonalności.
1┌────────────────────────────────────────────┐
2│ Core (Domain) │
3│ ┌──────────────────────────────────────┐ │
4│ │ Application │ │
5│ │ ┌──────────────────────────────┐ │ │
6│ │ │ Interfejs │ │ │
7│ │ │ (Controllers, Presenters...) │ │ │
8│ │ └──────────────────────────────┘ │ │
9│ └──────────────────────────────────────┘ │
10└────────────────────────────────────────────┘
11 │ │
12 ┌──────────┘ └──────────┐
13 │ │
14┌───▼────────────┐ ┌──────────▼───┐
15│ UI │ │ Infrastructure│
16│ (React, Vue..)│ │ (DB, API...) │
17└────────────────┘ └──────────────┘W kontekście Parku Jurajskiego:
Niezależnie od wybranej struktury folderów i architektury, kluczowe jest właściwe podzielenie kodu na mniejsze, spójne moduły.
Każdy moduł powinien mieć jasno określoną, pojedynczą odpowiedzialność:
1// dinosaurHealthMonitor.js - odpowiedzialny tylko za monitorowanie zdrowia dinozaurów
2export function checkVitalSigns(dinosaurId) {
3 //
4}
5
6export function alertHealthIssue(dinosaurId, issue) {
7 //
8}
9
10// enclosureSecurity.js - odpowiedzialny tylko za bezpieczeństwo wybiegów
11export function checkEnclosureSecurity(enclosureId) {
12 //
13}
14
15export function activateEmergencyProtocol(enclosureId) {
16 //
17}Moduły powinny komunikować się ze sobą przez czyste, dobrze zdefiniowane interfejsy:
1// Moduł wyższego poziomu korzysta z interfejsów modułów niższego poziomu
2import { checkVitalSigns } from './dinosaurHealthMonitor';
3import { checkEnclosureSecurity } from './enclosureSecurity';
4
5// parkSafetySystem.js
6export async function performSafetyCheck() {
7 const dinosaurs = await fetchAllDinosaurs();
8 const enclosures = await fetchAllEnclosures();
9
10 // Używamy interfejsów innych modułów
11 const healthIssues = dinosaurs.map(checkVitalSigns);
12 const securityIssues = enclosures.map(checkEnclosureSecurity);
13
14 return {
15 healthIssues: healthIssues.filter(issue => issue !== null),
16 securityIssues: securityIssues.filter(issue => issue !== null)
17 };
18}Wstrzykiwanie zależności to technika, która pozwala na odwrócenie zależności między modułami, co ułatwia testowanie i zmiany:
1// Bez DI
2function DinosaurMonitor() {
3 // Bezpośrednia zależność
4 const vitalSignsMonitor = new VitalSignsMonitor();
5
6 return {
7 checkDinosaur(dinosaur) {
8 return vitalSignsMonitor.check(dinosaur);
9 }
10 };
11}
12
13// Z DI
14function DinosaurMonitor(vitalSignsMonitor) {
15 return {
16 checkDinosaur(dinosaur) {
17 return vitalSignsMonitor.check(dinosaur);
18 }
19 };
20}
21
22// Możemy teraz wstrzyknąć różne implementacje
23const realMonitor = DinosaurMonitor(new VitalSignsMonitor());
24const testMonitor = DinosaurMonitor(new MockVitalSignsMonitor());W dużych projektach zarządzanie importami może stać się problematyczne. Oto kilka technik, które pomagają utrzymać je w porządku:
Pliki indeksowe, które re-eksportują wszystkie publiczne elementy z danego katalogu:
1// dinosaurMonitoring/index.js
2export * from './vitalSigns';
3export * from './behaviorAnalysis';
4export * from './healthAlerts';
5
6// W innym module
7import { checkVitalSigns, analyzeBehavior } from './dinosaurMonitoring';
8// Zamiast
9import { checkVitalSigns } from './dinosaurMonitoring/vitalSigns';
10import { analyzeBehavior } from './dinosaurMonitoring/behaviorAnalysis';Wiele narzędzi bundlujących (jak webpack, Vite) pozwala na definiowanie aliasów dla ścieżek importów:
1// webpack.config.js
2module.exports = {
3 resolve: {
4 alias: {
5 '@core': path.resolve(__dirname, 'src/core'),
6 '@features': path.resolve(__dirname, 'src/features'),
7 '@shared': path.resolve(__dirname, 'src/shared'),
8 }
9 }
10};
11
12// W kodzie
13import { activateProtocol } from '@core/security';
14import { DinosaurCard } from '@features/dinosaurs/components';To eliminuje problem względnych ścieżek (../../..) i sprawia, że importy są bardziej czytelne.
W większych aplikacjach zarządzanie stanem staje się kluczowym wyzwaniem. Oto kilka podejść:
Przechowywanie całego stanu aplikacji w jednym miejscu:
1// Akcje Redux dla systemu monitorowania dinozaurów
2const UPDATE_DINOSAUR_HEALTH = 'UPDATE_DINOSAUR_HEALTH';
3const ALERT_HEALTH_ISSUE = 'ALERT_HEALTH_ISSUE';
4
5// Reducer
6function dinosaurHealthReducer(state = initialState, action) {
7 switch (action.type) {
8 case UPDATE_DINOSAUR_HEALTH:
9 return {
10 ...state,
11 dinosaurs: state.dinosaurs.map(dino =>
12 dino.id === action.payload.id
13 ? { ...dino, health: action.payload.health }
14 : dino
15 )
16 };
17 case ALERT_HEALTH_ISSUE:
18 return {
19 ...state,
20 alerts: [...state.alerts, action.payload]
21 };
22 default:
23 return state;
24 }
25}Podzielenie stanu na mniejsze, niezależne "plasterki":
1// Slice stanu odpowiedzialny za dinozaury
2const dinosaurSlice = createSlice({
3 name: 'dinosaurs',
4 initialState: { list: [], loading: false, error: null },
5 reducers: {
6 fetchStart: (state) => {
7 state.loading = true;
8 },
9 fetchSuccess: (state, action) => {
10 state.list = action.payload;
11 state.loading = false;
12 },
13 updateHealth: (state, action) => {
14 const { id, health } = action.payload;
15 const dinosaur = state.list.find(d => d.id === id);
16 if (dinosaur) dinosaur.health = health;
17 }
18 }
19});
20
21// Slice stanu odpowiedzialny za wybiegi
22const enclosureSlice = createSlice({
23 name: 'enclosures',
24 initialState: { list: [], securityStatus: {} },
25 reducers: {
26 //
27 }
28});Rozróżnienie między danymi pochodzącymi z serwera a stanem UI:
1// Dane z serwera - obsługiwane przez React Query
2const { data: dinosaurs, isLoading, error } = useQuery(
3 'dinosaurs',
4 () => fetchDinosaurs()
5);
6
7// Stan UI - obsługiwany lokalnie
8const [selectedDinosaurId, setSelectedDinosaurId] = useState(null);W dużych aplikacjach, zarządzanie efektami ubocznymi (side effects) takimi jak żądania API, operacje I/O, etc. staje się skomplikowane. Oto kilka podejść:
1// Redux Saga dla monitorowania zdrowia dinozaurów
2function* monitorDinosaurHealthSaga() {
3 while (true) {
4 try {
5 // Pobierz wszystkie dinozaury
6 const dinosaurs = yield call(api.fetchAllDinosaurs);
7
8 // Sprawdź zdrowie każdego dinozaura
9 for (const dinosaur of dinosaurs) {
10 const healthData = yield call(api.fetchDinosaurHealth, dinosaur.id);
11
12 // Aktualizuj stan
13 yield put(updateDinosaurHealth(dinosaur.id, healthData));
14
15 // Jeśli zdrowie jest krytyczne, zgłoś alert
16 if (healthData.status === 'critical') {
17 yield put(alertHealthIssue(dinosaur.id, healthData));
18 }
19 }
20
21 // Poczekaj 5 minut przed kolejnym sprawdzeniem
22 yield delay(5 * 60 * 1000);
23 } catch (error) {
24 console.error('Health monitoring error:', error);
25 yield put(monitoringError(error));
26 // Poczekaj 1 minutę przed ponowną próbą
27 yield delay(60 * 1000);
28 }
29 }
30}1// Hook React Query do zarządzania danymi dinosaura i ich zdrowiem
2function useDinosaurHealth(dinosaurId) {
3 return useQuery(
4 ['dinosaurHealth', dinosaurId],
5 () => api.fetchDinosaurHealth(dinosaurId),
6 {
7 // Odświeżaj dane co 1 minutę
8 refetchInterval: 60 * 1000,
9 // Reaguj na zmiany zdrowia
10 onSuccess: (data) => {
11 if (data.status === 'critical') {
12 notifyHealthIssue(dinosaurId, data);
13 }
14 }
15 }
16 );
17}
18
19// W komponencie
20function DinosaurMonitor({ dinosaurId }) {
21 const { data, isLoading, error } = useDinosaurHealth(dinosaurId);
22
23 if (isLoading) return <Loading />;
24 if (error) return <Error message={error.message} />;
25
26 return (
27 <div className={`health-status ${data.status}`}>
28 <h2>Zdrowie dinozaura: {data.status}</h2>
29 <div>Temperatura: {data.temperature}°C</div>
30 <div>Tętno: {data.heartRate} BPM</div>
31 <div>Aktualizacja: {formatTime(data.timestamp)}</div>
32 </div>
33 );
34}W dużych projektach kompleksowe testowanie jest kluczowe. Oto kilka poziomów testów, które należy rozważyć:
Testowanie pojedynczych modułów w izolacji:
1// Test funkcji checkVitalSigns z modułu dinosaurHealthMonitor
2describe('dinosaurHealthMonitor', () => {
3 describe('checkVitalSigns', () => {
4 it('should correctly identify healthy dinosaurs', () => {
5 const healthyDino = {
6 id: 'trex1',
7 temperature: 38,
8 heartRate: 70,
9 bloodOxygen: 95
10 };
11
12 const result = checkVitalSigns(healthyDino);
13 expect(result.status).toBe('healthy');
14 expect(result.issues).toHaveLength(0);
15 });
16
17 it('should detect high temperature', () => {
18 const feverishDino = {
19 id: 'trex1',
20 temperature: 42, // Too high
21 heartRate: 70,
22 bloodOxygen: 95
23 };
24
25 const result = checkVitalSigns(feverishDino);
26 expect(result.status).toBe('issue');
27 expect(result.issues).toContain('high temperature');
28 });
29 });
30});Testowanie współpracy między modułami:
1// Test współpracy między modułem zdrowia i modułem alertów
2describe('Health monitoring integration', () => {
3 it('should generate alerts for health issues', async () => {
4 // Przygotuj mocki
5 const mockAlertSystem = {
6 sendAlert: jest.fn()
7 };
8
9 const mockDinosaurData = {
10 id: 'raptor2',
11 temperature: 43, // Krytycznie wysoka
12 heartRate: 120, // Przyspieszone tętno
13 bloodOxygen: 85 // Niski poziom tlenu
14 };
15
16 // Utwórz system monitorowania z zamockowanym systemem alertów
17 const healthMonitor = createHealthMonitor(mockAlertSystem);
18
19 // Wywołaj funkcję monitorującą
20 await healthMonitor.checkAndAlert(mockDinosaurData);
21
22 // Sprawdź, czy alert został wysłany
23 expect(mockAlertSystem.sendAlert).toHaveBeenCalledWith(
24 expect.objectContaining({
25 dinosaurId: 'raptor2',
26 severity: 'critical',
27 issues: expect.arrayContaining([
28 expect.stringMatching(/temperature/i),
29 expect.stringMatching(/heart rate/i),
30 expect.stringMatching(/oxygen/i)
31 ])
32 })
33 );
34 });
35});Testowanie pełnych ścieżek użytkownika:
1// Test E2E procesu reagowania na awarię wybiegu
2describe('Enclosure breach response', () => {
3 it('should activate emergency protocol on fence failure', async () => {
4 // Zaloguj się jako administrator systemu
5 await login('admin', 'password');
6
7 // Przejdź do panelu zarządzania wybiegami
8 await navigateTo('enclosures');
9
10 // Symuluj awarię płotu
11 await simulateFenceFailure('enclosure-b12');
12
13 // Sprawdź, czy alarm został aktywowany
14 const alarmStatus = await getElement('.alarm-status');
15 expect(alarmStatus).toHaveClass('active');
16
17 // Sprawdź, czy protokół awaryjny został uruchomiony
18 const protocolStatus = await getElement('.emergency-protocol');
19 expect(protocolStatus.textContent).toContain('Aktywny');
20
21 // Sprawdź, czy powiadomienia zostały wysłane
22 const notifications = await getElements('.notification');
23 expect(notifications).toHaveLength(2); // Do zespołu bezpieczeństwa i kierownictwa
24
25 // Symuluj naprawę płotu
26 await repairFence('enclosure-b12');
27
28 // Sprawdź, czy alarm został wyłączony
29 const updatedAlarmStatus = await getElement('.alarm-status');
30 expect(updatedAlarmStatus).not.toHaveClass('active');
31 });
32});W dużych projektach dokumentacja staje się niezbędna. Oto różne poziomy dokumentacji, które należy rozważyć:
1/**
2 * Sprawdza i ocenia zdrowie dinozaura na podstawie jego parametrów życiowych.
3 *
4 * @param {Object} dinosaur - Obiekt dinozaura do sprawdzenia
5 * @param {string} dinosaur.id - Unikalny identyfikator dinozaura
6 * @param {number} dinosaur.temperature - Temperatura ciała dinozaura w stopniach Celsjusza
7 * @param {number} dinosaur.heartRate - Tętno dinozaura w uderzeniach na minutę
8 * @param {number} dinosaur.bloodOxygen - Poziom tlenu we krwi dinozaura (0-100%)
9 *
10 * @returns {Object} Obiekt z oceną zdrowia
11 * @returns {string} returns.status - Status zdrowia: 'healthy', 'issue', 'critical'
12 * @returns {Array<string>} returns.issues - Lista wykrytych problemów zdrowotnych
13 *
14 * @example
15 * const healthAssessment = checkVitalSigns({
16 * id: 'trex1',
17 * temperature: 38.5,
18 * heartRate: 75,
19 * bloodOxygen: 92
20 * });
21 * // { status: 'healthy', issues: [] }
22 */
23function checkVitalSigns(dinosaur) {
24 // implementacja...
25}Każdy moduł powinien mieć swój własny plik README.md, który opisuje jego funkcjonalność, interfejs i użycie:
1# Moduł monitorowania zdrowia dinozaurów
2
3Ten moduł odpowiada za monitorowanie i ocenę stanu zdrowia dinozaurów w Parku Jurajskim.
4
5## Funkcjonalności
6
7- Regularne sprawdzanie parametrów życiowych dinozaurów
8- Wykrywanie problemów zdrowotnych i generowanie alertów
9- Historyczne śledzenie stanu zdrowia i trendów
10- Automatyczne powiadamianie personelu weterynaryjnego
11
12## API
13
14### `checkVitalSigns(dinosaur)`
15
16Sprawdza zdrowie dinozaura na podstawie jego parametrów życiowych.
17
18### `monitorHealth(dinosaurId, interval)`
19
20Rozpoczyna cykliczne monitorowanie zdrowia dinozaura.
21
22### `stopMonitoring(dinosaurId)`
23
24Zatrzymuje monitorowanie zdrowia dinozaura.
25
26## Przykłady użycia
27
28~~~js
29// Jednorazowe sprawdzenie zdrowia
30const healthStatus = checkVitalSigns(dinosaur);
31
32// Rozpoczęcie ciągłego monitorowania
33const stopMonitoring = monitorHealth('trex1', 60000); // co minutę
34
35// Zatrzymanie monitorowania
36stopMonitoring();Ten moduł korzysta z:
Jest używany przez:
1
2### 3. Dokumentacja architektury (diagragram, ADR)
3
4Dokumentowanie decyzji architektonicznych jest kluczowe dla zrozumienia, dlaczego system został zaprojektowany w określony sposób. Pomocne są tutaj:
5
6- **Diagramy architektury** (C4, UML)
7- **Architecture Decision Records (ADR)** - dokumenty opisujące ważne decyzje architektoniczne, ich kontekst i konsekwencje
8
9~~~text
10# ADR 001: Wybór Redux jako systemu zarządzania stanem
11
12## Kontekst
13
14Aplikacja zarządzania Parkiem Jurajskim potrzebuje mechanizmu zarządzania stanem, który obsłuży:
15- Złożony stan globalnz (dane o dinozaurach, wybiegach, zwiedzających)
16- Asynchroniczne operacje (komunikacja z systemami w parku)
17- Śledzenie historii zmian stanu (audyt bezpieczeństwa)
18
19## Decyzja
20
21Zdecydowaliśmy się użyć Redux z Redux Toolkit i Redux Saga jako głównego mechanizmu zarządzania stanem.
22
23## Uzasadnienie
24
25- **Przewidywalność**: Redux zapewnia jednokierunkowy przepływ danych
26- **Middleware**: Redux Saga ułatwia zarządzanie złożonymi operacjami asynchronicznymi
27- **DevTools**: Narzędzia deweloperskie Redux ułatwiają debugowanie
28- **Ekosystem**: Bogate ekosystem bibliotek i narzędzi
29- **Time Travel Debugging**: Możliwość śledzenia historii zmian stanu
30
31## Konsekwencje
32
33### Pozytywne
34- Jasny przepływ danych w aplikacji
35- Łatwiejsze debugowanie problemów ze stanem
36- Możliwość audytu działań użytkowników
37
38### Negatywne
39- Zwiększona ilość kodu (actions, reducers, selectors)
40- Wyższa krzywa uczenia dla nowych członków zespołu
41- Potencjalny overhead wydajnościowy przy niewłaściwym użyciuZdefiniuj i egzekwuj standardy kodowania za pomocą narzędzi jak ESLint, Prettier:
1// .eslintrc.json
2{
3 "extends": [
4 "eslint:recommended",
5 "plugin:react/recommended"
6 ],
7 "rules": {
8 "react/prop-types": "error",
9 "no-unused-vars": "error",
10 "no-console": ["warn", { "allow": ["warn", "error"] }],
11 "prefer-const": "error"
12 }
13}1// typescript
2interface DinosaurVitalSigns {
3 id: string;
4 temperature: number;
5 heartRate: number;
6 bloodOxygen: number;
7}
8
9interface HealthAssessment {
10 status: 'healthy' | 'issue' | 'critical';
11 issues: string[];
12 lastChecked: Date;
13}
14
15function checkVitalSigns(dinosaur: DinosaurVitalSigns): HealthAssessment {
16 // implementacja...
17}Skonfiguruj potoki CI/CD, które automatycznie uruchamiają testy, analizę statyczną kodu i wdrażają aplikację:
1# .github/workflows/ci.yml
2name: CI/CD
3
4on:
5 push:
6 branches: [ main, develop ]
7 pull_request:
8 branches: [ main, develop ]
9
10jobs:
11 test:
12 runs-on: ubuntu-latest
13 steps:
14 - uses: actions/checkout@v2
15 - name: Set up Node.js
16 uses: actions/setup-node@v2
17 with:
18 node-version: '14'
19 - name: Install dependencies
20 run: npm ci
21 - name: Run tests
22 run: npm test
23 - name: Run linting
24 run: npm run lint
25 - name: Build
26 run: npm run build
27
28 deploy:
29 needs: test
30 if: github.ref == 'refs/heads/main'
31 runs-on: ubuntu-latest
32 steps:
33 - uses: actions/checkout@v2
34 # ... kroki wdrożeniaUstanów proces Code Review, który zapewni, że kod jest sprawdzany przez co najmniej jednego innego programistę przed włączeniem do głównej gałęzi.
Implementuj monitorowanie wydajności aplikacji, aby szybko wykrywać i naprawiać problemy:
1// Przykład konfiguracji własnego narzędzia do monitorowania wydajności
2const performanceMonitor = {
3 eventTiming: {},
4
5 startTiming(eventName) {
6 this.eventTiming[eventName] = {
7 start: performance.now()
8 };
9 },
10
11 endTiming(eventName) {
12 if (this.eventTiming[eventName]) {
13 const start = this.eventTiming[eventName].start;
14 const duration = performance.now() - start;
15
16 // Loguj do serwera monitorowania
17 logPerformanceMetric(eventName, duration);
18
19 // Generuj alert, jeśli operacja trwała zbyt długo
20 if (duration > performanceThresholds[eventName]) {
21 alertPerformanceIssue(eventName, duration);
22 }
23
24 delete this.eventTiming[eventName];
25 }
26 }
27};
28
29// Użycie
30function loadDinosaurData(id) {
31 performanceMonitor.startTiming('loadDinosaurData');
32
33 return fetchDinosaurData(id)
34 .then(data => {
35 performanceMonitor.endTiming('loadDinosaurData');
36 return data;
37 })
38 .catch(error => {
39 performanceMonitor.endTiming('loadDinosaurData');
40 throw error;
41 });
42}Strukturyzacja kodu w większych projektach JavaScript to złożone zagadnienie, które wymaga przemyślanego podejścia. Kluczowe aspekty to:
Stosując te zasady, możesz uniknąć chaosu, który mógłby prowadzić do "katastrofy" w twoim projekcie JavaScript - podobnie jak odpowiednia organizacja i zarządzanie pomogłyby uniknąć katastrofy w Parku Jurajskim!