Cours JavaScript et TypeScript · Module 5: JavaScript avancé
Points d'arrêt et exécution pas à pas du code
Dans cette leçon8
Dans l'exercice précédent, nous avons découvert la console du navigateur et ses fonctions de journalisation. Cependant, même les meilleurs messages de console peuvent être insuffisants face à des bugs complexes dans le système de gestion de Jurassic Park. Et si la fonction validateDinosaurContainment() renvoyait un résultat incorrect sans qu'on sache pourquoi ? Et si l'algorithme de suivi du comportement des prédateurs contenait un bug subtil ?
Dans ces cas-là, on a besoin de pouvoir mettre en pause l'exécution du code à un point précis et d'analyser l'état de l'application. C'est là qu'interviennent les points d'arrêt (breakpoints) et l'exécution pas à pas - parmi les techniques de débogage JavaScript les plus puissantes.
Que sont les points d'arrêt ?
Un point d'arrêt est un marqueur placé dans le code qui indique au navigateur d'arrêter l'exécution lorsqu'il atteint ce point. Cela permet d'examiner en détail l'état de l'application à un moment choisi - on peut vérifier les valeurs des variables, inspecter la pile d'appels et exécuter le code pas à pas.
Placer des points d'arrêt dans le navigateur
Il existe plusieurs façons de placer des points d'arrêt dans le navigateur :
1. Points d'arrêt dans le panneau Sources/Débogueur
La méthode la plus simple consiste à utiliser le panneau Sources (Chrome) ou le panneau Débogueur (Firefox) :
- Ouvre les outils de développement (<kbd>F12</kbd> ou <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>I</kbd>)
- Va dans l'onglet « Sources » (Chrome) ou « Débogueur » (Firefox)
- Trouve et ouvre le fichier où tu veux placer un point d'arrêt
- Clique sur le numéro de la ligne où tu veux mettre l'exécution en pause
- Une flèche ou un marqueur bleu apparaît au point sélectionné, indiquant un point d'arrêt actif
Par exemple, on peut vouloir mettre l'exécution en pause lors de la vérification de l'état des clôtures à Jurassic Park :
1function checkFenceStatus(sectorId) {
2 const sector = getSector(sectorId); // Place un point d'arrêt sur cette ligne
3
4 if (!sector) {
5 throw new Error(`Secteur inconnu : ${sectorId}`);
6 }
7
8 const fenceStatus = sector.fence.getStatus();
9
10 if (fenceStatus !== 'active') {
11 triggerAlarm(sectorId, `Panne de clôture : ${fenceStatus}`);
12 }
13
14 return fenceStatus;
15}Après avoir placé le point d'arrêt, quand checkFenceStatus() est appelée, le navigateur met l'exécution en pause à cette ligne et on peut vérifier la valeur de sectorId et des autres variables disponibles.
2. Points d'arrêt programmatiques avec l'instruction debugger
Parfois, il est plus pratique de placer un point d'arrêt directement dans le code avec l'instruction debugger :
1function trackDinosaur(dinosaurId, location) {
2 const dinosaur = getDinosaurById(dinosaurId);
3
4 debugger; // Le code se mettra en pause ici lorsque la fonction sera appelée
5
6 if (!dinosaur) {
7 console.error(`Dinosaure introuvable avec l'ID : ${dinosaurId}`);
8 return false;
9 }
10
11 updateDinosaurLocation(dinosaurId, location);
12 checkContainmentStatus(dinosaurId, location);
13
14 return true;
15}L'instruction debugger est ignorée lorsque les outils de développement ne sont pas ouverts, mais lorsqu'ils sont actifs, le navigateur met l'exécution en pause à ce point.
3. Points d'arrêt conditionnels
Parfois, on veut mettre l'exécution en pause uniquement quand une certaine condition est remplie - par exemple, lors du suivi d'un dinosaure avec un ID précis ou quand le niveau de danger dépasse une valeur :
Configuration via l'interface du navigateur
- Place un point d'arrêt classique en cliquant sur le numéro de ligne
- Clic droit sur le point d'arrêt
- Sélectionne « Edit breakpoint » ou « Add condition »
- Saisis une condition, par exemple :
dinosaurId === 'TRX-01'oudangerLevel > 8
Pause conditionnelle programmatique
On peut aussi utiliser une construction conditionnelle avec l'instruction debugger :
1function monitorDinosaurBehavior(dinosaur) {
2 const behaviorPattern = analyzeBehavior(dinosaur);
3 const dangerLevel = assessThreatLevel(behaviorPattern);
4
5 // Met l'exécution en pause uniquement si le niveau de danger est élevé
6 if (dangerLevel > 8) {
7 debugger; // Ne se mettra en pause que pour des niveaux de danger élevés
8
9 // Code pour gérer les comportements dangereux
10 activateEmergencyProtocol(dinosaur.id, dangerLevel);
11 }
12
13 return {
14 dinosaurId: dinosaur.id,
15 behaviorPattern,
16 dangerLevel,
17 timestamp: new Date()
18 };
19}Exécution pas à pas du code
Quand le code est en pause à un point d'arrêt, on dispose de plusieurs options d'exécution pas à pas qui permettent de parcourir le code de manière contrôlée :
1. Step Over (F10)
Exécute la ligne courante et s'arrête à la ligne suivante dans la même fonction. Si la ligne courante contient un appel de fonction, cette fonction est exécutée entièrement (on n'entre pas dedans).
1function monitorParkSystems() {
2 checkSecuritySystems(); // Step Over exécute toute cette fonction...
3 checkDinosaurContainment(); // ...et s'arrête ici
4 checkVisitorSafety();
5}2. Step Into (F11)
Exécute la ligne courante, mais si la ligne contient un appel de fonction, le débogueur « entre » dans cette fonction et s'arrête à sa première ligne.
1function monitorParkSystems() {
2 checkSecuritySystems(); // Step Into entrera dans cette fonction...
3 checkDinosaurContainment(); // ...au lieu de passer ici
4 checkVisitorSafety();
5}
6
7function checkSecuritySystems() {
8 const fenceStatus = checkFences(); // ...et s'arrêtera ici
9 const cameraStatus = checkCameras();
10 const gateStatus = checkGates();
11
12 return { fenceStatus, cameraStatus, gateStatus };
13}3. Step Out (Shift+F11)
Exécute le reste de la fonction courante et s'arrête après le retour de la fonction, à l'endroit où elle a été appelée.
1function checkSecuritySystems() {
2 const fenceStatus = checkFences();
3 const cameraStatus = checkCameras(); // Si on est ici...
4 const gateStatus = checkGates();
5
6 return { fenceStatus, cameraStatus, gateStatus };
7}
8
9function monitorParkSystems() {
10 checkSecuritySystems(); // ...Step Out exécute le reste de la fonction et s'arrête ici
11 checkDinosaurContainment();
12 checkVisitorSafety();
13}4. Continue (F8)
Reprend l'exécution du code jusqu'au prochain point d'arrêt rencontré ou jusqu'à la fin du script.
Inspecter les variables et l'état
Quand le code est en pause à un point d'arrêt, on peut examiner en profondeur l'état de l'application :
1. Panneau Scope/Variables
L'un des panneaux les plus importants pendant le débogage - il affiche toutes les variables disponibles dans la portée courante :
- Local - les variables locales de la fonction courante
- Closure - les variables provenant des fermetures (fonctions parentes)
- Global - les variables globales
- this - la valeur de
thisdans le contexte courant
Par exemple, lors du débogage d'une fonction surveillant le comportement d'un dinosaure :
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; // pause ici
9
10 // À ce point, le panneau Scope affichera :
11 // - Local : dinosaur, environmentalFactors, species, previousBehaviors, lastFeeding,
12 // weather, timeOfDay, nearbyDinosaurs, timeSinceFeeding, isHungry
13 // - Global : objets et fonctions globaux
14
15 const behaviorPrediction = predictBehavior(
16 species, previousBehaviors, isHungry, weather, timeOfDay, nearbyDinosaurs
17 );
18
19 return {
20 dinosaurId: dinosaur.id,
21 prediction: behaviorPrediction,
22 riskLevel: assessRiskLevel(behaviorPrediction),
23 isHungry,
24 timestamp: new Date()
25 };
26}2. Expressions de surveillance (Watch)
Le panneau Watch permet d'ajouter des expressions à surveiller pendant le débogage. C'est particulièrement utile pour des expressions complexes ou des valeurs non directement visibles dans le panneau Scope :
1// On peut ajouter des expressions surveillées comme :
2dinosaur.species === 'Velociraptor'
3getDistanceFromFence(dinosaur.location)
4isInDangerZone(dinosaur.location, restrictedSectors)3. La console en mode débogage
Pendant la pause à un point d'arrêt, la console fonctionne dans le contexte de la position courante du code. On peut exécuter des expressions qui ont accès à toutes les variables visibles dans la portée courante :
1// Dans la console, on peut taper :
2dinosaur.species
3timeSinceFeeding / (1000 * 60 * 60) // Convertit les millisecondes en heures
4nearbyDinosaurs.filter(d => d.species === 'Velociraptor')C'est une façon puissante d'explorer interactivement l'état de l'application et de mener des expériences sans modifier le code.
Techniques pratiques de débogage pour Jurassic Park
1. Déboguer du code asynchrone
Les systèmes de Jurassic Park utilisent souvent des opérations asynchrones - récupération de données depuis des capteurs, communication avec les systèmes de sécurité, etc. Déboguer ce genre de code demande des techniques particulières :
1async function monitorsEnvironmentalSensors() {
2 try {
3 console.log("Démarrage de la surveillance des capteurs...");
4
5 // Récupère les données des capteurs de manière asynchrone
6 const temperatureData = await fetchSensorData('temperature');
7 debugger; // Le point d'arrêt se déclenche après réception de la réponse
8
9 const humidityData = await fetchSensorData('humidity');
10 const windData = await fetchSensorData('wind');
11
12 const environmentalRisk = analyzeEnvironmentalRisk(
13 temperatureData, humidityData, windData
14 );
15
16 if (environmentalRisk.level === 'high') {
17 notifyParkManagement(`Risque environnemental élevé détecté : ${environmentalRisk.reason}`);
18 }
19
20 return environmentalRisk;
21 } catch (error) {
22 console.error("Erreur de surveillance des capteurs :", error);
23 triggerBackupSystems();
24 return { level: 'unknown', reason: 'error' };
25 }
26}Dans les outils de développement modernes, les points d'arrêt fonctionnent correctement avec les fonctions async - l'exécution se mettra en pause au point debugger, même après des opérations await.
2. Mettre en pause sur les exceptions
Dans un système complexe comme Jurassic Park, les erreurs peuvent apparaître à des endroits inattendus. Au lieu de placer manuellement des points d'arrêt, on peut configurer le débogueur pour qu'il se mette en pause lorsqu'une exception se produit :
- Ouvre le panneau Sources/Débogueur
- Trouve la section « Breakpoints »
- Coche « Pause on exceptions »
- Optionnellement : coche « Pause on caught exceptions »
Le navigateur mettra alors l'exécution en pause à chaque exception non gérée, ce qui simplifie grandement l'identification de la source des problèmes.
3. Source maps
Dans les applications de production, le code JavaScript est souvent minifié et regroupé, ce qui rend le débogage difficile. Les source maps permettent aux navigateurs d'afficher et de déboguer le code source d'origine, même lorsque c'est le code minifié qui s'exécute :
1// webpack.config.js
2module.exports = {
3 devtool: 'source-map', // Génère des source maps pour tous les fichiers
4};Pour Jurassic Park, où la sécurité est une priorité, c'est une fonctionnalité essentielle qui permet de déboguer rapidement les problèmes en production.
Le débogage en pratique - étude de cas
Analysons un exemple pratique de débogage d'un problème dans le système de surveillance des dinosaures :
1class DinosaurMonitoringSystem {
2 constructor() {
3 this.activeMonitors = new Map();
4 this.alertLevel = 'normal';
5 }
6
7 checkDinosaurStatus(dinosaurId) {
8 const monitor = this.activeMonitors.get(dinosaurId);
9
10 if (!monitor) {
11 console.error(`Aucune surveillance active pour le dinosaure ${dinosaurId}`);
12 return null;
13 }
14
15 try {
16 const dinosaur = getDinosaurById(dinosaurId);
17 const location = getLastKnownLocation(dinosaurId);
18 const vitalSigns = getVitalSigns(dinosaurId);
19
20 // BUG : On ne vérifie pas si location existe !
21 const inContainment = isInContainmentZone(location.coordinates);
22
23 const reading = {
24 timestamp: Date.now(),
25 location,
26 vitalSigns,
27 inContainment
28 };
29
30 monitor.readings.push(reading);
31 monitor.locationStatus = inContainment ? 'contained' : 'breach';
32
33 return reading;
34 } catch (error) {
35 console.error(`Erreur lors de la vérification de l'état du dinosaure ${dinosaurId} :`, error);
36 return null;
37 }
38 }
39}Étape 1 : identifier le problème
Pendant les tests, le système de surveillance signale de façon inattendue un niveau d'alerte « critical » pour certains dinosaures, alors qu'ils sont en sécurité dans leurs enclos.
Étape 2 : placer des points d'arrêt
On place un point d'arrêt dans checkDinosaurStatus pour enquêter :
1checkDinosaurStatus(dinosaurId) {
2 const monitor = this.activeMonitors.get(dinosaurId);
3 if (!monitor) { return null; }
4
5 debugger; // Place le point d'arrêt ici pour examiner toutes les variables
6
7 try { /* ...reste du code */ }
8}Étape 3 : avancer pas à pas et analyser
- Avance dans le code avec « Step Into » et « Step Over »
- On atteint la ligne qui vérifie l'état de l'emplacement :
1const inContainment = isInContainmentZone(location.coordinates); - Vérifie la variable
locationdans le panneau Scope - elle vautnull - Problème identifié :
getLastKnownLocationrenvoie parfoisnullquand il ne trouve pas l'emplacement, et le code ne vérifie pas ce cas avant d'accéder àlocation.coordinates
Étape 4 : corriger le bug
1// Code plus sûr avec vérification
2const location = getLastKnownLocation(dinosaurId);
3const inContainment = location && location.coordinates
4 ? isInContainmentZone(location.coordinates)
5 : false; // Si on ne connaît pas l'emplacement, on suppose le pireÉtape 5 : vérifier la solution
Après avoir corrigé le bug, retire le point d'arrêt et teste à nouveau le système. Cette fois, il fonctionne correctement sans signaler de fausses alarmes.
Bonnes pratiques de débogage
- Utilise des noms de variables explicites - cela facilite l'identification des variables pendant le débogage
- Ajoute de la documentation aux fonctions - les commentaires JSDoc aident à comprendre l'objectif et les valeurs attendues
- Structure le code de manière logique - un code propre est plus facile à déboguer
- Pratique la programmation défensive - valide les entrées et gère les cas limites
- Crée de petites fonctions dédiées - les fonctions à responsabilité unique sont plus faciles à déboguer
- Utilise try-catch pour le code potentiellement problématique - cela aide à localiser les sources d'erreurs
- Évite de modifier les variables débogées - cela peut changer le comportement du programme
Résumé
Les points d'arrêt et l'exécution pas à pas sont des outils précieux dans l'arsenal de tout développeur travaillant sur les systèmes de Jurassic Park. Ils permettent de :
- Mettre l'exécution en pause à des moments critiques
- Examiner l'état de l'application, les valeurs des variables et le contexte d'exécution
- Exécuter le code pas à pas pour comprendre son déroulement
- Identifier les sources d'erreurs, surtout dans les systèmes complexes
- Vérifier la justesse des fonctions et des modules
Souviens-toi qu'à Jurassic Park, la capacité à trouver et corriger rapidement les bugs peut être une question de vie ou de mort - aussi bien pour le code que (dans un vrai parc) pour les visiteurs !
Code de cette leçon : main.js
1// Circular Dependencies - Park Jurajski
2// Handling circular dependencies in modules
3
4console.log("Circular Dependencies (Circular Dependencies)");
5console.log("Jak handle mutual module dependencies\n");
6
7// ===========================================
8// PROBLEM: Circular Dependencies
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: we need SecuritySystem, but it also needs DinosaurManager
20 console.log(`Added dinosaura: ${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(`Security level: ${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: we need SecuritySystem, which needs DinosaurManager
75 if (this.securitySystem) {
76 this.securitySystem.alertForDinosaur(this);
77 }
78 }
79}
80
81// ===========================================
82// SOLUTION 1: Dependency Injection
83// ===========================================
84
85console.log("=== SOLUTION 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("\nChecking security...");
99security.checkSecurity();
100
101console.log("\nSound test:");
102trex.roar();
103raptor.roar();
104brachi.roar();
105
106// ===========================================
107// SOLUTION 2: Event System
108// ===========================================
109
110console.log("\n=== SOLUTION 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 // Listen for events
139 this.eventBus.on('dinosaur:added', (dino) => {
140 console.log(`Event: Added ${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 // Listen for events
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 dinosaurs z event system:");
178managerV2.addDinosaur({ id: '1', name: 'Rex Jr', dangerLevel: 8 });
179managerV2.addDinosaur({ id: '2', name: 'Herbivore', dangerLevel: 2 });
180
181// ===========================================
182// SOLUTION 3: Lazy Loading / Late Binding
183// ===========================================
184
185console.log("\n=== SOLUTION 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: Added ${dinosaur.name}`);
200
201 // Lazy evaluation - security is available only now
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 has not been assigned yet");
224 return;
225 }
226
227 const dinosaurs = this._dinosaurManager.getAllDinosaurs();
228 console.log(`Checking security for ${dinosaurs.length} dinosaurs`);
229 }
230}
231
232const managerV3 = new DinosaurManagerV3();
233const securityV3 = new SecuritySystemV3();
234
235// Set references after creating both objects
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(" - Pass dependencies through constructor/parameters");
244console.log(" - Testowalne i przewidywalne\n");
245
246console.log("Event System:");
247console.log(" - Loose coupling between modules");
248console.log(" - Scalable for many subscribers\n");
249
250console.log("Lazy Loading:");
251console.log(" - Delayed dependency binding");
252console.log(" - Good for optional dependencies\n");
253
254console.log("UNIKAJ:");
255console.log(" - Direct require/import creating cycles");
256console.log(" - Global variables as solutions");
257console.log(" - Tight coupling between modules\n");
258
259console.log("Demonstracja completed!");Tu as repéré une erreur dans cette leçon ?
Tâches pratiques dans le jeu
- Tri vertical
Ordonne les étapes du débogage de code, du début à la fin :