Cours JavaScript et TypeScript · Module 5: JavaScript avancé
Projet final : système de diagnostic de Jurassic Park
Dans cette leçon4
Bienvenue dans l'exercice final du Module 5 ! Nous allons construire un système de diagnostic de Jurassic Park complet qui applique tout ce qu'on a appris : classes d'erreurs personnalisées, système de journalisation professionnel, blocs try/catch imbriqués, clause finally et architecture de code modulaire.
« On a besoin d'un système capable de scanner tous les sous-systèmes du parc et de générer un rapport de diagnostic complet », dit le Dr Ellie Sattler. « Clôtures, alimentation, capteurs - et quand quelque chose tourne mal, le système doit le journaliser, le classer par gravité et recommander une réponse. »
Architecture du système
Le système de diagnostic se compose de quatre modules :
- Hiérarchie d'erreurs personnalisée - ParkError, FenceError, PowerError, SensorError, SecurityBreachError
- Système de journalisation - ParkLogger avec des niveaux de log (DEBUG, INFO, WARN, ERROR, FATAL)
- Module de diagnostic - ParkDiagnostics qui scanne les sous-systèmes avec try/catch/finally imbriqués
- Module de reporting - DiagnosticsReporter qui agrège les résultats et génère des recommandations
1. Hiérarchie d'erreurs personnalisée
1// Erreur de base pour tous les problèmes du parc
2class ParkError extends Error {
3 constructor(message, code, sector, severity = "medium") {
4 super(message);
5 this.name = "ParkError";
6 this.code = code;
7 this.sector = sector;
8 this.severity = severity;
9 this.timestamp = new Date().toISOString();
10 this.resolved = false;
11
12 if (Error.captureStackTrace) {
13 Error.captureStackTrace(this, this.constructor);
14 }
15 }
16
17 resolve() {
18 this.resolved = true;
19 this.resolvedAt = new Date().toISOString();
20 return this;
21 }
22
23 toReport() {
24 return {
25 name: this.name,
26 message: this.message,
27 code: this.code,
28 sector: this.sector,
29 severity: this.severity,
30 timestamp: this.timestamp,
31 resolved: this.resolved
32 };
33 }
34}
35
36// Erreur de clôture - sécurité physique
37class FenceError extends ParkError {
38 constructor(sector, fenceId, integrity) {
39 const severity = integrity < 50 ? "critical" : integrity < 70 ? "high" : "medium";
40 super(
41 `Panne de clôture ${fenceId} dans le secteur ${sector} : intégrité ${integrity}%`,
42 "FENCE_FAILURE",
43 sector,
44 severity
45 );
46 this.name = "FenceError";
47 this.fenceId = fenceId;
48 this.integrity = integrity;
49 }
50}
51
52// Erreur d'alimentation - infrastructure
53class PowerError extends ParkError {
54 constructor(sector, powerLevel, backupAvailable) {
55 const severity = !backupAvailable && powerLevel < 30 ? "critical" : "high";
56 super(
57 `Panne de courant dans le secteur ${sector} : ${powerLevel}%. Secours : ${backupAvailable ? "disponible" : "indisponible"}`,
58 "POWER_FAILURE",
59 sector,
60 severity
61 );
62 this.name = "PowerError";
63 this.powerLevel = powerLevel;
64 this.backupAvailable = backupAvailable;
65 }
66}
67
68// Erreur de capteur
69class SensorError extends ParkError {
70 constructor(sector, sensorId, issue) {
71 super(
72 `Panne du capteur ${sensorId} dans le secteur ${sector} : ${issue}`,
73 "SENSOR_FAILURE",
74 sector,
75 "medium"
76 );
77 this.name = "SensorError";
78 this.sensorId = sensorId;
79 this.issue = issue;
80 }
81}
82
83// Brèche de sécurité - toujours critique
84class SecurityBreachError extends ParkError {
85 constructor(sector, breachType, speciesInvolved) {
86 super(
87 `BRÈCHE DE SÉCURITÉ dans ${sector} : ${breachType} - espèce : ${speciesInvolved}`,
88 "SECURITY_BREACH",
89 sector,
90 "critical"
91 );
92 this.name = "SecurityBreachError";
93 this.breachType = breachType;
94 this.speciesInvolved = speciesInvolved;
95 }
96}Chaque type d'erreur porte des informations contextuelles supplémentaires, ce qui facilite le diagnostic et la réparation.
2. Système de journalisation (Logger avec niveaux)
Le deuxième module est un système de journalisation professionnel qui enregistre tous les événements avec la gravité et le contexte appropriés.
1// Niveaux de log
2const LogLevel = {
3 DEBUG: 0,
4 INFO: 1,
5 WARN: 2,
6 ERROR: 3,
7 FATAL: 4
8};
9
10const LogLevelNames = ["DEBUG", "INFO", "WARN", "ERROR", "FATAL"];
11
12class ParkLogger {
13 constructor(moduleName, minLevel = LogLevel.DEBUG) {
14 this.moduleName = moduleName;
15 this.minLevel = minLevel;
16 this.logs = [];
17 this.maxLogs = 1000; // Limite de logs stockés
18 }
19
20 _log(level, message, context = {}) {
21 if (level < this.minLevel) return;
22
23 const entry = {
24 timestamp: new Date().toISOString(),
25 level: LogLevelNames[level],
26 module: this.moduleName,
27 message,
28 context
29 };
30
31 this.logs.push(entry);
32
33 // Rotation des logs - retire les plus anciens
34 if (this.logs.length > this.maxLogs) {
35 this.logs = this.logs.slice(-this.maxLogs);
36 }
37
38 // Formate la sortie
39 const prefix = `[${entry.timestamp}] [${entry.level}] [${this.moduleName}]`;
40 const contextStr = Object.keys(context).length > 0
41 ? ` | ${JSON.stringify(context)}`
42 : "";
43
44 switch (level) {
45 case LogLevel.DEBUG: console.log(`${prefix} ${message}${contextStr}`); break;
46 case LogLevel.INFO: console.info(`${prefix} ${message}${contextStr}`); break;
47 case LogLevel.WARN: console.warn(`${prefix} ${message}${contextStr}`); break;
48 case LogLevel.ERROR: console.error(`${prefix} ${message}${contextStr}`); break;
49 case LogLevel.FATAL: console.error(`${prefix} ${message}${contextStr}`); break;
50 }
51
52 return entry;
53 }
54
55 debug(message, context) { return this._log(LogLevel.DEBUG, message, context); }
56 info(message, context) { return this._log(LogLevel.INFO, message, context); }
57 warn(message, context) { return this._log(LogLevel.WARN, message, context); }
58 error(message, context) { return this._log(LogLevel.ERROR, message, context); }
59 fatal(message, context) { return this._log(LogLevel.FATAL, message, context); }
60
61 // Filtre les logs par niveau
62 getLogsByLevel(level) {
63 const levelName = LogLevelNames[level];
64 return this.logs.filter(log => log.level === levelName);
65 }
66
67 // Statistiques des logs
68 getStats() {
69 const stats = { total: this.logs.length };
70 LogLevelNames.forEach(name => {
71 stats[name] = this.logs.filter(l => l.level === name).length;
72 });
73 return stats;
74 }
75}Le logger prend en charge le filtrage par niveau, stocke l'historique des événements et fait automatiquement tourner les anciens logs.
3. Module de diagnostic (scan et try/catch/finally)
Le troisième module est le cœur du système - il scanne les différents sous-systèmes du parc, capture et classe les erreurs, puis prend les mesures correctives appropriées.
1class ParkDiagnostics {
2 constructor() {
3 this.logger = new ParkLogger("Diagnostics", LogLevel.DEBUG);
4 this.errors = [];
5 this.scanResults = [];
6 }
7
8 // Simule le scan d'une clôture
9 scanFence(sector, fenceId) {
10 this.logger.info(`Scan de la clôture ${fenceId} dans le secteur ${sector}`);
11
12 try {
13 const integrity = Math.round(40 + Math.random() * 60);
14
15 if (integrity < 80) {
16 throw new FenceError(sector, fenceId, integrity);
17 }
18
19 this.logger.debug(`Clôture ${fenceId} OK : ${integrity}%`, { sector, fenceId });
20 return { status: "OK", integrity };
21
22 } catch (error) {
23 if (error instanceof FenceError) {
24 this.errors.push(error);
25 this.logger.error(error.message, error.toReport());
26
27 if (error.severity === "critical") {
28 this.logger.fatal(`Panne de clôture CRITIQUE ! Évacuez le secteur ${sector} !`);
29 this._initiateEmergencyProtocol(sector);
30 }
31 } else {
32 // Erreur inattendue - journalise et relance
33 this.logger.fatal(`Erreur inattendue pendant le scan : ${error.message}`);
34 throw error;
35 }
36
37 return { status: "FAILURE", error: error.toReport() };
38
39 } finally {
40 this.logger.debug(`Scan de la clôture ${fenceId} terminé`);
41 }
42 }
43
44 // Scan de l'alimentation avec try/catch imbriqués
45 scanPower(sector) {
46 this.logger.info(`Scan de l'alimentation dans le secteur ${sector}`);
47
48 try {
49 const mainPower = Math.round(Math.random() * 100);
50 let backupStatus = false;
51
52 try {
53 // Tente de vérifier l'alimentation de secours
54 backupStatus = Math.random() > 0.3;
55 if (!backupStatus && mainPower < 30) {
56 throw new PowerError(sector, mainPower, false);
57 }
58 } catch (innerError) {
59 if (innerError instanceof PowerError) {
60 this.errors.push(innerError);
61 this.logger.error(`Problème d'alimentation : ${innerError.message}`);
62 } else {
63 throw innerError; // Transmet les erreurs inconnues au catch externe
64 }
65 }
66
67 if (mainPower < 50) {
68 throw new PowerError(sector, mainPower, backupStatus);
69 }
70
71 return { status: "OK", mainPower, backupActive: backupStatus };
72
73 } catch (error) {
74 if (error instanceof PowerError) {
75 this.errors.push(error);
76 this.logger.warn(error.message, error.toReport());
77 return { status: "WARNING", error: error.toReport() };
78 }
79 throw error;
80 }
81 }
82
83 // Diagnostic complet d'un secteur
84 runFullDiagnostics(sector) {
85 this.logger.info(`=== Diagnostic complet pour le secteur : ${sector} ===`);
86 const startTime = performance.now();
87
88 const result = {
89 sector,
90 fences: [],
91 power: null,
92 errors: [],
93 duration: 0
94 };
95
96 try {
97 // Scanne les clôtures (3 clôtures par secteur)
98 for (let i = 1; i <= 3; i++) {
99 const fenceResult = this.scanFence(sector, `FENCE-${sector}-${i}`);
100 result.fences.push(fenceResult);
101 }
102
103 // Scanne l'alimentation
104 result.power = this.scanPower(sector);
105
106 } catch (unexpectedError) {
107 this.logger.fatal(`Erreur de diagnostic inattendue : ${unexpectedError.message}`);
108 result.criticalFailure = true;
109 } finally {
110 result.duration = `${(performance.now() - startTime).toFixed(2)}ms`;
111 result.errors = this.errors.filter(e => e.sector === sector).map(e => e.toReport());
112 this.scanResults.push(result);
113 this.logger.info(`Diagnostic ${sector} terminé en ${result.duration}`);
114 }
115
116 return result;
117 }
118
119 _initiateEmergencyProtocol(sector) {
120 this.logger.fatal(`PROTOCOLE D'URGENCE pour le secteur ${sector} - envoi des notifications`);
121 }
122}Remarque les blocs try/catch imbriqués et l'utilisation de finally pour enregistrer la durée de l'opération quel que soit le résultat.
4. Module de reporting (agrégation et analyse des données)
Le dernier module collecte les données de tous les diagnostics et génère un rapport lisible avec des recommandations.
1class DiagnosticsReporter {
2 constructor(diagnostics) {
3 this.diagnostics = diagnostics;
4 this.logger = new ParkLogger("Reporting", LogLevel.INFO);
5 }
6
7 generateReport() {
8 const allErrors = this.diagnostics.errors;
9 const scanResults = this.diagnostics.scanResults;
10
11 // Regroupe les erreurs par type
12 const errorsByType = {};
13 allErrors.forEach(error => {
14 const type = error.name;
15 if (!errorsByType[type]) errorsByType[type] = [];
16 errorsByType[type].push(error.toReport());
17 });
18
19 // Regroupe les erreurs par secteur
20 const errorsBySector = {};
21 allErrors.forEach(error => {
22 if (!errorsBySector[error.sector]) errorsBySector[error.sector] = [];
23 errorsBySector[error.sector].push(error.toReport());
24 });
25
26 // Compte par gravité
27 const severityCounts = { low: 0, medium: 0, high: 0, critical: 0 };
28 allErrors.forEach(error => {
29 severityCounts[error.severity]++;
30 });
31
32 const report = {
33 generatedAt: new Date().toISOString(),
34 summary: {
35 totalErrors: allErrors.length,
36 criticalErrors: severityCounts.critical,
37 sectorsScanned: scanResults.length,
38 overallStatus: severityCounts.critical > 0 ? "CRITICAL" :
39 severityCounts.high > 0 ? "WARNING" : "STABLE"
40 },
41 errorsByType,
42 errorsBySector,
43 severityCounts,
44 recommendations: this._generateRecommendations(allErrors, severityCounts)
45 };
46
47 this._printReport(report);
48 return report;
49 }
50
51 _generateRecommendations(errors, severity) {
52 const recommendations = [];
53
54 if (severity.critical > 0) {
55 recommendations.push("IMMÉDIATEMENT : Évacuer les secteurs avec des pannes de clôture critiques");
56 recommendations.push("Déployer des équipes de réparation vers les clôtures endommagées");
57 }
58
59 if (severity.high > 0) {
60 recommendations.push("URGENT : Inspecter les clôtures à intégrité réduite");
61 }
62
63 const fenceErrors = errors.filter(e => e instanceof FenceError);
64 if (fenceErrors.length > 3) {
65 recommendations.push("Inspection systématique de toutes les clôtures - plusieurs pannes détectées");
66 }
67
68 const powerErrors = errors.filter(e => e instanceof PowerError);
69 if (powerErrors.length > 0) {
70 recommendations.push("Vérifier les générateurs de secours dans tous les secteurs");
71 }
72
73 if (recommendations.length === 0) {
74 recommendations.push("Tous les systèmes stables - poursuivre la surveillance standard");
75 }
76
77 return recommendations;
78 }
79
80 _printReport(report) {
81 console.log("\n" + "=".repeat(60));
82 console.log(" RAPPORT DE DIAGNOSTIC DE JURASSIC PARK");
83 console.log("=".repeat(60));
84 console.log(`Généré : ${report.generatedAt}`);
85 console.log(`Statut : ${report.summary.overallStatus}`);
86 console.log(`Secteurs scannés : ${report.summary.sectorsScanned}`);
87 console.log(`Erreurs détectées : ${report.summary.totalErrors} (critiques : ${report.summary.criticalErrors})`);
88
89 console.log("\nErreurs par type :");
90 Object.entries(report.errorsByType).forEach(([type, errors]) => {
91 console.log(` ${type} : ${errors.length}`);
92 });
93
94 console.log("\nRecommandations :");
95 report.recommendations.forEach((rec, i) => {
96 console.log(` ${i + 1}. ${rec}`);
97 });
98
99 console.log("=".repeat(60));
100 }
101}
102
103// Lance le système de diagnostic
104const diagnostics = new ParkDiagnostics();
105
106const sectors = ["T-Rex-Paddock", "Raptor-Pen", "Herbivore-Valley"];
107sectors.forEach(sector => diagnostics.runFullDiagnostics(sector));
108
109const reporter = new DiagnosticsReporter(diagnostics);
110reporter.generateReport();
111
112// Statistiques du logger
113console.log("\nStatistiques des logs :", diagnostics.logger.getStats());Conseils d'implémentation
Quand tu construis le système de diagnostic, garde ces principes à l'esprit :
La hiérarchie d'erreurs doit refléter le domaine :
1// Chaque type d'erreur = un genre de problème du parc
2ParkError // base - erreur générale du parc
3├── FenceError // clôtures - sécurité physique
4├── PowerError // alimentation - infrastructure
5├── SensorError // capteurs - surveillance
6└── SecurityBreachError // brèches - priorité maximaleUtilise instanceof pour distinguer les types d'erreurs :
1try {
2 riskyOperation();
3} catch (error) {
4 if (error instanceof SecurityBreachError) {
5 // Réponse immédiate
6 } else if (error instanceof FenceError) {
7 // Protocole de réparation
8 } else if (error instanceof ParkError) {
9 // Gestion d'erreur générale du parc
10 } else {
11 // Erreur inconnue - journalise et escalade
12 throw error;
13 }
14}Utilise toujours finally pour le nettoyage :
1function scanWithCleanup() {
2 const connection = openConnection();
3 try {
4 return performScan(connection);
5 } catch (error) {
6 logError(error);
7 return null;
8 } finally {
9 // Ferme toujours la connexion - même en cas d'erreur
10 connection.close();
11 }
12}Bonne chance !
Ce projet est l'occasion de prouver que tu sais construire des systèmes robustes et bien organisés. À Jurassic Park, il n'y a pas de place pour les exceptions non gérées - chaque erreur doit être capturée, journalisée et gérée avant que la situation ne dégénère. Comme l'a dit John Hammond : « Nous n'avons reculé devant aucune dépense ! » - et tu ne devrais reculer devant aucun effort quand il s'agit de gestion des erreurs.
Crée ton système de diagnostic dans l'éditeur ci-dessous. Implémente tes propres types d'erreurs, ton système de journalisation et un mécanisme de diagnostic pour au moins deux sous-systèmes du parc.
Code de cette leçon : index.js
1// Projet final : systeme de diagnostic de Jurassic Park
2// Combine : modules, gestion des erreurs, try/catch, erreurs personnalisees
3
4// === 1. HIERARCHIE DES ERREURS ===
5// TODO : cree une classe ParkError etendant Error
6class ParkError extends Error {
7 constructor(message, code, sector, severity) {
8 super(message);
9 this.name = "ParkError";
10 // TODO : affecte code, sector, severity, timestamp
11 }
12
13 // TODO : methode toReport() renvoyant un objet rapport
14}
15
16// TODO : cree les classes derivees : FenceError, PowerError, SystemError
17class FenceError extends ParkError {
18 constructor(message, sector, fenceId) {
19 // TODO : appelle super avec les bons parametres
20 }
21}
22
23// === 2. SYSTEME DE JOURNALISATION ===
24// TODO : cree un objet Logger avec les niveaux : debug, info, warn, error
25const Logger = {
26 logs: [],
27 // TODO : methodes log(level, message, context)
28 // TODO : methode getLogsByLevel(level)
29 // TODO : methode exportLogs()
30};
31
32// === 3. MODULE DE DIAGNOSTIC ===
33// TODO : cree des fonctions de diagnostic utilisant try/catch/finally
34function scanSubsystem(subsystemName) {
35 // TODO : simule l'analyse du sous-systeme
36 // TODO : utilise try/catch pour intercepter les erreurs
37 // TODO : utilise finally pour journaliser la fin de l'analyse
38}
39
40// === 4. GENERATEUR DE RAPPORTS ===
41// TODO : cree une fonction generateDiagnosticReport()
42// Rassemble les donnees de tous les sous-systemes
43// Gere les erreurs de chaque sous-systeme separement
44
45function generateDiagnosticReport() {
46 console.log("=== RAPPORT DE DIAGNOSTIC ===");
47 const subsystems = ["Clotures", "Alimentation", "Surveillance", "Communication"];
48 // TODO : analyse chaque sous-systeme avec gestion des erreurs
49 console.log("=== FIN DU RAPPORT ===");
50}
51
52// Lance le diagnostic :
53generateDiagnosticReport();Tu as repéré une erreur dans cette leçon ?