Skip to content

Latest commit

 

History

History
1106 lines (854 loc) · 31.1 KB

File metadata and controls

1106 lines (854 loc) · 31.1 KB

Guide de Débogage - MiniSheet

📋 Table des Matières

  1. Introduction
  2. Outils de Débogage
  3. Débogage des Formules
  4. Débogage de l'Interface Utilisateur
  5. Débogage des Erreurs
  6. Débogage des Performances
  7. Cas d'Usage Spécifiques
  8. Techniques Avancées
  9. Checklist de Débogage
  10. FAQ

Introduction

Ce guide vous aidera à déboguer efficacement MiniSheet. Il couvre les outils, techniques et stratégies pour identifier et résoudre les problèmes courants.

Types de Problèmes Courants

  • 🔴 Erreurs de formule : Syntaxe invalide, références circulaires, fonctions inconnues
  • 🟡 Problèmes d'UI : Rendu incorrect, événements non capturés, sélection défectueuse
  • 🟠 Erreurs de sérialisation : Échec de sauvegarde/chargement
  • 🔵 Problèmes de performance : Recalcul lent, interface qui lag
  • 🟣 Erreurs de logique : Calculs incorrects, comportement inattendu

Outils de Débogage

1. println! et eprintln!

Utilisation de base :

// Afficher une valeur
println!("Valeur de la cellule ({}, {}): {:?}", r, c, value);

// Afficher avec formatage
println!("Formula: {} -> Result: {}", formula, result);

// Erreurs sur stderr
eprintln!("ERREUR: Échec de l'évaluation de la cellule ({}, {})", r, c);

Exemple dans le code :

fn eval_cell(&mut self, r: usize, c: usize, stack: &mut HashSet<(usize, usize)>) 
    -> Result<CellValue, CellValue> {
    println!("[DEBUG] Évaluation de la cellule ({}, {})", r, c);
    
    if stack.contains(&(r, c)) {
        eprintln!("[ERREUR] Référence circulaire détectée: ({}, {})", r, c);
        return Err(CellValue::Error("Circular reference".into()));
    }
    
    // ... reste du code
}

Avantages :

  • ✅ Simple et rapide
  • ✅ Pas de configuration nécessaire
  • ✅ Fonctionne partout

Inconvénients :

  • ❌ Pollue la sortie
  • ❌ Difficile à filtrer
  • ❌ Pas de niveaux de log

2. dbg! Macro

Utilisation :

let formula = "=A1+B2";
dbg!(&formula);  // Affiche: [src/main.rs:123] &formula = "=A1+B2"

let result = parse_expr(&mut parser);
dbg!(result);  // Affiche la valeur et le type

Exemple pratique :

fn parse_expr(&mut self, ...) -> Result<f64, CellValue> {
    dbg!(self.pos, self.peek());  // Affiche la position et le caractère actuel
    
    let value = self.parse_additive(sheet, stack)?;
    dbg!(value);  // Affiche la valeur calculée
    
    Ok(value)
}

Avantages :

  • ✅ Affiche automatiquement le fichier et la ligne
  • ✅ Affiche le nom de la variable
  • ✅ Formatage automatique

Inconvénients :

  • ❌ Prend possession de la valeur (utiliser & pour les références)
  • ❌ Peut être verbeux

3. Debug Conditionnel

Utilisation de #[cfg(debug_assertions)] :

#[cfg(debug_assertions)]
fn debug_print(&self, msg: &str) {
    println!("[DEBUG] {}", msg);
}

#[cfg(debug_assertions)]
fn debug_cell(&self, r: usize, c: usize) {
    println!("[DEBUG] Cellule ({}, {}): {:?}", r, c, self.get_cell(r, c));
}

// Dans le code
#[cfg(debug_assertions)]
{
    debug_print("Début de l'évaluation");
    debug_cell(0, 0);
}

Avantages :

  • ✅ N'apparaît que dans les builds debug
  • ✅ Pas d'impact sur les builds release
  • ✅ Peut être désactivé facilement

4. GDB (GNU Debugger)

Installation :

# Windows (via MSYS2/MinGW)
pacman -S mingw-w64-x86_64-gdb

# Linux
sudo apt-get install gdb

# macOS
brew install gdb

Utilisation de base :

# Compiler avec symboles de debug
cargo build

# Lancer avec GDB
gdb target/debug/mini_sheet

# Commandes GDB utiles
(gdb) break main                    # Breakpoint au début
(gdb) break Sheet::eval_cell       # Breakpoint sur une fonction
(gdb) break src/main.rs:2140       # Breakpoint à une ligne spécifique
(gdb) run                          # Exécuter le programme
(gdb) continue                     # Continuer l'exécution
(gdb) next                         # Ligne suivante
(gdb) step                         # Entrer dans la fonction
(gdb) print variable_name          # Afficher une variable
(gdb) print *self                  # Afficher self
(gdb) backtrace                    # Afficher la pile d'appels
(gdb) info locals                  # Afficher les variables locales
(gdb) watch variable_name          # Surveiller une variable
(gdb) quit                         # Quitter

Exemple de session :

$ gdb target/debug/mini_sheet
(gdb) break Sheet::eval_cell
Breakpoint 1 at 0x123456: file src/main.rs, line 2134.
(gdb) run
Starting program: target/debug/mini_sheet
...
Breakpoint 1, Sheet::eval_cell (self=0x7fff..., r=0, c=0, ...) at src/main.rs:2134
(gdb) print r
$1 = 0
(gdb) print c
$2 = 0
(gdb) print self.raw
$3 = HashMap with 1 entry
(gdb) continue

5. VS Code avec CodeLLDB

Configuration .vscode/launch.json :

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug MiniSheet",
            "cargo": {
                "args": ["build", "--bin=mini_sheet"],
                "filter": {
                    "name": "mini_sheet",
                    "kind": "bin"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}",
            "env": {},
            "sourceLanguages": ["rust"]
        }
    ]
}

Utilisation :

  1. Placer un breakpoint (clic gauche dans la marge)
  2. Appuyer sur F5 pour démarrer le débogage
  3. Utiliser les contrôles (Continue, Step Over, Step Into, Step Out)
  4. Inspecter les variables dans le panneau "Variables"
  5. Utiliser la console de débogage pour évaluer des expressions

Raccourcis clavier :

  • F5 : Démarrer/Continuer
  • F10 : Step Over
  • F11 : Step Into
  • Shift+F11 : Step Out
  • Shift+F5 : Arrêter

6. Logging avec log et env_logger

Ajout des dépendances (Cargo.toml) :

[dependencies]
log = "0.4"
env_logger = "0.11"

Configuration dans le code :

use log::{debug, info, warn, error, trace};

fn main() -> eframe::Result<()> {
    // Initialiser le logger
    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug"))
        .init();
    
    // ...
}

// Dans le code
fn eval_cell(&mut self, r: usize, c: usize, stack: &mut HashSet<(usize, usize)>) 
    -> Result<CellValue, CellValue> {
    trace!("Début de l'évaluation de la cellule ({}, {})", r, c);
    debug!("Stack actuel: {:?}", stack);
    
    if stack.contains(&(r, c)) {
        error!("Référence circulaire détectée: ({}, {})", r, c);
        return Err(CellValue::Error("Circular reference".into()));
    }
    
    info!("Évaluation réussie pour ({}, {})", r, c);
    Ok(value)
}

Utilisation :

# Tous les logs (trace, debug, info, warn, error)
RUST_LOG=debug cargo run

# Seulement les erreurs
RUST_LOG=error cargo run

# Logs spécifiques au module
RUST_LOG=mini_sheet=debug cargo run

# Combinaison
RUST_LOG=debug,mini_sheet::sheet=trace cargo run

Niveaux de log :

  • trace! : Très détaillé (tout)
  • debug! : Informations de débogage
  • info! : Informations générales
  • warn! : Avertissements
  • error! : Erreurs

Débogage des Formules

Problème : Formule Non Évaluée

Symptômes :

  • La cellule affiche la formule au lieu du résultat
  • Pas d'erreur visible

Débogage :

fn commit_formula_bar(&mut self) {
    let (r, c) = self.primary_cell();
    let formula = self.formula_bar.clone();
    
    // Debug: vérifier que la formule commence par =
    if !formula.trim_start().starts_with('=') {
        eprintln!("[ERREUR] La formule ne commence pas par =: {}", formula);
        return;
    }
    
    self.sheet.set_raw(r, c, formula.clone());
    
    // Debug: vérifier l'évaluation
    let mut stack = HashSet::new();
    match self.sheet.eval_cell(r, c, &mut stack) {
        Ok(value) => {
            println!("[DEBUG] Formule '{}' évaluée à: {:?}", formula, value);
        }
        Err(e) => {
            eprintln!("[ERREUR] Échec de l'évaluation: {:?}", e);
        }
    }
}

Vérifications :

  1. ✅ La formule commence-t-elle par = ?
  2. ✅ La formule est-elle stockée correctement dans Sheet::raw ?
  3. eval_cell est-il appelé ?
  4. ✅ Y a-t-il une erreur de parsing ?

Problème : Erreur de Syntaxe

Symptômes :

  • Message d'erreur "#ERR: Invalid syntax"
  • La formule semble correcte

Débogage dans FormulaParser :

fn parse_expr(&mut self, sheet: &mut Sheet, stack: &mut HashSet<(usize, usize)>) 
    -> Result<f64, CellValue> {
    let start_pos = self.pos;
    let start_input = self.input;
    
    println!("[DEBUG] Parsing expression à la position {}: '{}'", 
             start_pos, &self.input[start_pos..]);
    
    match self.parse_additive(sheet, stack) {
        Ok(value) => {
            println!("[DEBUG] Expression parsée avec succès: {}", value);
            Ok(value)
        }
        Err(e) => {
            eprintln!("[ERREUR] Échec du parsing à la position {}: {:?}", start_pos, e);
            eprintln!("[ERREUR] Contexte: '{}'", &self.input[start_pos.min(10)..start_pos+20.min(self.input.len())]);
            Err(e)
        }
    }
}

Points à vérifier :

  1. ✅ Parenthèses équilibrées ?
  2. ✅ Opérateurs valides ?
  3. ✅ Références de cellules correctes ?
  4. ✅ Arguments de fonctions corrects ?

Problème : Référence Circulaire

Symptômes :

  • Message "#ERR: Circular reference"
  • Le calcul ne se termine jamais (dans certains cas)

Débogage :

fn eval_cell(&mut self, r: usize, c: usize, stack: &mut HashSet<(usize, usize)>) 
    -> Result<CellValue, CellValue> {
    // Debug: afficher la stack
    println!("[DEBUG] Stack actuel: {:?}", stack);
    println!("[DEBUG] Tentative d'évaluation de ({}, {})", r, c);
    
    if stack.contains(&(r, c)) {
        // Construire le chemin de la référence circulaire
        let mut cycle_path = Vec::new();
        for cell in stack.iter() {
            cycle_path.push(*cell);
        }
        cycle_path.push((r, c));
        
        eprintln!("[ERREUR] Référence circulaire détectée:");
        for (i, (r, c)) in cycle_path.iter().enumerate() {
            eprintln!("  {} -> ({}, {}) = {}", i, r, c, self.addr(*r, *c));
        }
        
        return Err(CellValue::Error(format!(
            "Circular reference: {}",
            cycle_path.iter()
                .map(|(r, c)| self.addr(*r, *c))
                .collect::<Vec<_>>()
                .join(" -> ")
        )));
    }
    
    stack.insert((r, c));
    
    // ... évaluation ...
    
    stack.remove(&(r, c));
    Ok(value)
}

Vérifications :

  1. ✅ La cellule s'auto-référence-t-elle ?
  2. ✅ Y a-t-il une chaîne de dépendances qui boucle ?
  3. ✅ Le stack est-il correctement géré (insert/remove) ?

Problème : Fonction Inconnue

Symptômes :

  • Message "#ERR: Unknown function: XXX"
  • La fonction existe pourtant

Débogage :

fn parse_function(&mut self, name: &str, sheet: &mut Sheet, stack: &mut HashSet<(usize, usize)>) 
    -> Result<f64, CellValue> {
    println!("[DEBUG] Parsing fonction: '{}'", name);
    
    // Normaliser le nom (uppercase)
    let name_upper = name.to_uppercase();
    println!("[DEBUG] Nom normalisé: '{}'", name_upper);
    
    match name_upper.as_str() {
        "SUM" => {
            println!("[DEBUG] Fonction SUM détectée");
            // ...
        }
        "AVG" | "AVERAGE" => {
            println!("[DEBUG] Fonction AVG/AVERAGE détectée");
            // ...
        }
        _ => {
            eprintln!("[ERREUR] Fonction inconnue: '{}' (normalisé: '{}')", name, name_upper);
            Err(CellValue::Error(format!("Unknown function: {}", name)))
        }
    }
}

Vérifications :

  1. ✅ Le nom de la fonction est-il en majuscules ?
  2. ✅ Y a-t-il des espaces ou caractères invisibles ?
  3. ✅ La fonction est-elle bien dans le match ?

Problème : Calcul Incorrect

Symptômes :

  • Le résultat est différent de ce qui est attendu
  • Pas d'erreur visible

Débogage étape par étape :

fn parse_additive(&mut self, sheet: &mut Sheet, stack: &mut HashSet<(usize, usize)>) 
    -> Result<f64, CellValue> {
    let mut left = self.parse_multiplicative(sheet, stack)?;
    println!("[DEBUG] parse_additive: left = {}", left);
    
    self.skip_ws();
    
    loop {
        if self.matches("+") {
            self.advance(1);
            let right = self.parse_multiplicative(sheet, stack)?;
            println!("[DEBUG] parse_additive: {} + {} = {}", left, right, left + right);
            left += right;
        } else if self.matches("-") {
            self.advance(1);
            let right = self.parse_multiplicative(sheet, stack)?;
            println!("[DEBUG] parse_additive: {} - {} = {}", left, right, left - right);
            left -= right;
        } else {
            break;
        }
    }
    
    println!("[DEBUG] parse_additive: résultat final = {}", left);
    Ok(left)
}

Vérifications :

  1. ✅ Les opérandes sont-ils correctement parsés ?
  2. ✅ L'ordre des opérations est-il respecté ?
  3. ✅ Les parenthèses sont-elles correctement gérées ?
  4. ✅ Les références de cellules sont-elles correctement résolues ?

Débogage de l'Interface Utilisateur

Problème : Cellule Non Affichée

Symptômes :

  • La cellule devrait avoir une valeur mais est vide
  • Le rendu ne se met pas à jour

Débogage :

fn render_grid_content(&self, ...) {
    for r in visible_rows {
        for c in visible_cols {
            let value = self.sheet.get_cell(r, c);
            
            // Debug: vérifier que la valeur existe
            #[cfg(debug_assertions)]
            {
                let raw = self.sheet.get_raw(r, c);
                if !raw.is_empty() && value.is_empty() {
                    eprintln!("[ERREUR] Cellule ({}, {}) a une valeur raw '{}' mais get_cell retourne Empty", 
                             r, c, raw);
                }
            }
            
            // Rendu de la cellule
            // ...
        }
    }
}

Vérifications :

  1. get_cell retourne-t-il la bonne valeur ?
  2. ✅ Le rectangle de rendu est-il correct ?
  3. ✅ La cellule est-elle dans la zone visible ?
  4. ✅ Y a-t-il un problème de cache ?

Problème : Sélection Non Fonctionnelle

Symptômes :

  • Clic sur une cellule ne la sélectionne pas
  • Sélection multiple ne fonctionne pas

Débogage :

fn handle_cell_click(&mut self, r: usize, c: usize, modifiers: &egui::Modifiers) {
    println!("[DEBUG] Clic sur la cellule ({}, {})", r, c);
    println!("[DEBUG] Modifiers: Shift={}, Ctrl={}, Alt={}", 
             modifiers.shift, modifiers.ctrl, modifiers.alt);
    
    match &mut self.selection {
        Selection::Single(old_r, old_c) => {
            println!("[DEBUG] Ancienne sélection: Single({}, {})", old_r, old_c);
        }
        Selection::Range(start, end) => {
            println!("[DEBUG] Ancienne sélection: Range(({}, {}), ({}, {}))", 
                     start.0, start.1, end.0, end.1);
        }
        Selection::Multiple(cells) => {
            println!("[DEBUG] Ancienne sélection: Multiple({} cellules)", cells.len());
        }
    }
    
    // Logique de sélection
    // ...
    
    println!("[DEBUG] Nouvelle sélection: {:?}", self.selection);
}

Vérifications :

  1. ✅ L'événement de clic est-il capturé ?
  2. ✅ Les coordonnées sont-elles correctes ?
  3. ✅ Les modificateurs (Shift, Ctrl) sont-ils détectés ?
  4. ✅ La sélection est-elle mise à jour dans self.selection ?

Problème : Raccourcis Clavier Non Fonctionnels

Symptômes :

  • Ctrl+C, Ctrl+V, etc. ne fonctionnent pas

Débogage :

fn handle_keyboard(&mut self, ctx: &egui::Context) {
    let input = ctx.input(|i| i.clone());
    
    // Debug: afficher toutes les touches pressées
    for key in input.keys_down.iter() {
        println!("[DEBUG] Touche pressée: {:?}", key);
    }
    
    // Vérifier les combinaisons
    if input.modifiers.ctrl {
        println!("[DEBUG] Ctrl est pressé");
        if input.key_pressed(egui::Key::C) {
            println!("[DEBUG] Ctrl+C détecté");
            self.copy();
        }
        if input.key_pressed(egui::Key::V) {
            println!("[DEBUG] Ctrl+V détecté");
            self.paste();
        }
    }
}

Vérifications :

  1. ✅ Les événements clavier sont-ils capturés ?
  2. ✅ Les modificateurs sont-ils détectés ?
  3. ✅ Y a-t-il un conflit avec d'autres raccourcis ?
  4. ✅ Le focus est-il sur la bonne fenêtre ?

Débogage des Erreurs

Problème : Erreur de Sérialisation

Symptômes :

  • Message "Error serializing data" lors de la sauvegarde
  • Fichier corrompu ou vide

Débogage :

fn save(&mut self) {
    println!("[DEBUG] Début de la sauvegarde");
    
    // Debug: vérifier les données
    println!("[DEBUG] Nombre de cellules: {}", self.sheet.raw.len());
    println!("[DEBUG] Nombre de formats: {}", self.sheet.formats.len());
    
    // Essayer de sérialiser
    match serde_json::to_string(&self.sheet) {
        Ok(json) => {
            println!("[DEBUG] Sérialisation réussie, taille: {} bytes", json.len());
            println!("[DEBUG] JSON (premiers 200 caractères): {}", 
                     &json.chars().take(200).collect::<String>());
            
            // Écrire le fichier
            match fs::write(&path, json) {
                Ok(_) => {
                    println!("[DEBUG] Fichier écrit avec succès");
                    self.set_status("Saved", StatusType::Ok);
                }
                Err(e) => {
                    eprintln!("[ERREUR] Échec de l'écriture: {:?}", e);
                    self.set_status(&format!("Error writing file: {}", e), StatusType::Error);
                }
            }
        }
        Err(e) => {
            eprintln!("[ERREUR] Échec de la sérialisation: {:?}", e);
            eprintln!("[ERREUR] Type d'erreur: {}", e);
            
            // Essayer de sérialiser avec pretty printing pour voir où ça échoue
            match serde_json::to_string_pretty(&self.sheet) {
                Ok(pretty) => {
                    eprintln!("[ERREUR] JSON (pretty, premiers 500 caractères):\n{}", 
                             &pretty.chars().take(500).collect::<String>());
                }
                Err(e2) => {
                    eprintln!("[ERREUR] Même avec pretty printing: {:?}", e2);
                }
            }
            
            self.set_status(&format!("Error serializing data: {}", e), StatusType::Error);
        }
    }
}

Vérifications :

  1. ✅ Toutes les structures sont-elles Serialize ?
  2. ✅ Y a-t-il des valeurs NaN ou Infinity dans les nombres ?
  3. ✅ Les HashMap avec clés (usize, usize) utilisent-elles le module de sérialisation personnalisé ?
  4. ✅ Y a-t-il des références circulaires dans les données ?

Problème : Erreur de Désérialisation

Symptômes :

  • Message d'erreur lors du chargement
  • Fichier ne se charge pas

Débogage :

fn open(&mut self) {
    // ... sélection du fichier ...
    
    match fs::read_to_string(&path) {
        Ok(content) => {
            println!("[DEBUG] Fichier lu, taille: {} bytes", content.len());
            println!("[DEBUG] Contenu (premiers 500 caractères):\n{}", 
                     &content.chars().take(500).collect::<String>());
            
            // Essayer de désérialiser
            match serde_json::from_str::<Sheet>(&content) {
                Ok(sheet) => {
                    println!("[DEBUG] Désérialisation réussie");
                    println!("[DEBUG] Cellules chargées: {}", sheet.raw.len());
                    self.sheet = sheet;
                    self.set_status("File loaded", StatusType::Ok);
                }
                Err(e) => {
                    eprintln!("[ERREUR] Échec de la désérialisation: {:?}", e);
                    eprintln!("[ERREUR] Position de l'erreur (si disponible): {:?}", 
                             e.line(), e.column());
                    
                    // Essayer de valider le JSON d'abord
                    match serde_json::from_str::<serde_json::Value>(&content) {
                        Ok(_) => {
                            eprintln!("[ERREUR] Le JSON est valide mais ne correspond pas au schéma Sheet");
                        }
                        Err(json_err) => {
                            eprintln!("[ERREUR] Le JSON est invalide: {:?}", json_err);
                        }
                    }
                    
                    self.set_status(&format!("Error loading file: {}", e), StatusType::Error);
                }
            }
        }
        Err(e) => {
            eprintln!("[ERREUR] Échec de la lecture du fichier: {:?}", e);
            self.set_status(&format!("Error reading file: {}", e), StatusType::Error);
        }
    }
}

Vérifications :

  1. ✅ Le fichier est-il un JSON valide ?
  2. ✅ Le schéma correspond-il à la structure Sheet ?
  3. ✅ Les modules de désérialisation personnalisés sont-ils corrects ?
  4. ✅ Y a-t-il des valeurs invalides (NaN, Infinity) ?

Débogage des Performances

Problème : Recalcul Lent

Symptômes :

  • L'application lag lors de la saisie
  • Le recalcul prend plusieurs secondes

Débogage avec timing :

use std::time::Instant;

fn recalc_all(&mut self) {
    let start = Instant::now();
    println!("[DEBUG] Début du recalcul");
    
    self.computed.clear();
    
    // Compter les cellules à recalculer
    let cell_count = self.raw.len();
    println!("[DEBUG] Nombre de cellules à recalculer: {}", cell_count);
    
    // Recalculer toutes les cellules
    let mut recalculated = 0;
    for (r, c) in self.raw.keys() {
        let _ = self.eval_cell(*r, *c, &mut HashSet::new());
        recalculated += 1;
        
        if recalculated % 100 == 0 {
            println!("[DEBUG] Recalculé {} cellules...", recalculated);
        }
    }
    
    let duration = start.elapsed();
    println!("[DEBUG] Recalcul terminé en {:?} ({:.2} ms/cellule)", 
             duration, duration.as_millis() as f64 / cell_count as f64);
}

Optimisations possibles :

  1. ✅ Implémenter le recalcul incrémental (seulement les cellules affectées)
  2. ✅ Utiliser un cache plus efficace
  3. ✅ Paralléliser le recalcul (si possible)
  4. ✅ Éviter les recalculs inutiles

Problème : Rendu Lent

Symptômes :

  • L'interface est lente à répondre
  • Le scrolling est saccadé

Débogage :

fn render_grid_content(&self, ...) {
    let start = Instant::now();
    
    // Compter les cellules rendues
    let mut rendered = 0;
    
    for r in visible_rows {
        for c in visible_cols {
            // Rendu de la cellule
            // ...
            rendered += 1;
        }
    }
    
    let duration = start.elapsed();
    if duration.as_millis() > 16 {  // Plus de 16ms = moins de 60 FPS
        eprintln!("[WARNING] Rendu lent: {} cellules en {:?} ({:.2} ms/cellule)", 
                 rendered, duration, duration.as_millis() as f64 / rendered as f64);
    }
}

Optimisations possibles :

  1. ✅ Virtualiser le rendu (seulement les cellules visibles)
  2. ✅ Réduire les allocations dans la boucle de rendu
  3. ✅ Utiliser le caching pour les cellules statiques
  4. ✅ Optimiser les calculs de position

Cas d'Usage Spécifiques

Cas 1 : Déboguer une Formule Complexe

Exemple : =IF(SUM(A1:A10)>100, AVERAGE(B1:B10), MIN(C1:C10))

fn parse_function(&mut self, name: &str, ...) -> Result<f64, CellValue> {
    match name.to_uppercase().as_str() {
        "IF" => {
            println!("[DEBUG] Parsing IF");
            
            let condition = self.parse_expr(sheet, stack)?;
            println!("[DEBUG] Condition IF: {}", condition);
            
            self.expect_separator()?;
            let true_value = self.parse_expr(sheet, stack)?;
            println!("[DEBUG] Valeur si vrai: {}", true_value);
            
            self.expect_separator()?;
            let false_value = self.parse_expr(sheet, stack)?;
            println!("[DEBUG] Valeur si faux: {}", false_value);
            
            let result = if condition != 0.0 { true_value } else { false_value };
            println!("[DEBUG] Résultat IF: {}", result);
            
            Ok(result)
        }
        // ...
    }
}

Cas 2 : Déboguer le Fill Handle

Problème : Les formules ne s'ajustent pas correctement

fn fill_range(&mut self, ...) {
    println!("[DEBUG] Fill range de ({}, {}) à ({}, {})", start_r, start_c, end_r, end_c);
    
    // Détecter le type de séquence
    let sequence_type = self.detect_sequence_type(start_r, start_c, count);
    println!("[DEBUG] Type de séquence détecté: {:?}", sequence_type);
    
    for i in 0..count {
        let target_r = start_r + i;
        let target_c = start_c;
        
        let source_value = self.get_raw(start_r, start_c);
        println!("[DEBUG] Valeur source pour la cellule ({}, {}): '{}'", 
                 target_r, target_c, source_value);
        
        if source_value.trim_start().starts_with('=') {
            // C'est une formule, ajuster les références
            let adjusted = self.adjust_formula_references(&source_value[1..], 
                                                          start_r, start_c, 
                                                          target_r, target_c);
            println!("[DEBUG] Formule ajustée: '={}'", adjusted);
            self.set_raw(target_r, target_c, format!("={}", adjusted));
        } else {
            // Valeur simple, utiliser la séquence
            let next_value = self.next_sequence_value(&source_value, i, &sequence_type);
            println!("[DEBUG] Valeur suivante: '{}'", next_value);
            self.set_raw(target_r, target_c, next_value);
        }
    }
}

Cas 3 : Déboguer la Recherche et Remplacement

Problème : La recherche ne trouve pas les résultats

fn perform_search(&mut self) {
    println!("[DEBUG] Recherche de: '{}'", self.search_text);
    
    self.search_results.clear();
    
    // Parcourir toutes les cellules
    for ((r, c), value) in &self.sheet.raw {
        println!("[DEBUG] Vérification de la cellule ({}, {}): '{}'", r, c, value);
        
        if value.contains(&self.search_text) {
            println!("[DEBUG] Match trouvé dans ({}, {})", r, c);
            self.search_results.push((*r, *c));
        }
    }
    
    println!("[DEBUG] {} résultats trouvés", self.search_results.len());
    
    if !self.search_results.is_empty() {
        self.current_search_index = 0;
        self.navigate_to_search_result(0);
    }
}

Techniques Avancées

1. Assertions de Débogage

fn eval_cell(&mut self, r: usize, c: usize, stack: &mut HashSet<(usize, usize)>) 
    -> Result<CellValue, CellValue> {
    // Assertions pour vérifier les invariants
    debug_assert!(r < self.rows, "Row index out of bounds: {} >= {}", r, self.rows);
    debug_assert!(c < self.cols, "Column index out of bounds: {} >= {}", c, self.cols);
    debug_assert!(!stack.contains(&(r, c)), "Cell already in stack: ({}, {})", r, c);
    
    // ...
}

2. Tracing avec tracing

Ajout de tracing (Cargo.toml) :

[dependencies]
tracing = "0.1"
tracing-subscriber = "0.3"

Configuration :

use tracing::{debug, info, warn, error, span, Level};

fn main() -> eframe::Result<()> {
    tracing_subscriber::fmt()
        .with_max_level(Level::DEBUG)
        .init();
    
    // ...
}

// Dans le code
fn eval_cell(&mut self, r: usize, c: usize, stack: &mut HashSet<(usize, usize)>) 
    -> Result<CellValue, CellValue> {
    let span = span!(Level::DEBUG, "eval_cell", row = r, col = c);
    let _enter = span.enter();
    
    debug!("Début de l'évaluation");
    
    // ...
    
    debug!("Évaluation terminée: {:?}", value);
    Ok(value)
}

3. Profiling avec perf (Linux)

# Compiler en mode release avec symboles
RUSTFLAGS="-g" cargo build --release

# Profiler
perf record -g target/release/mini_sheet

# Analyser
perf report

4. Memory Profiling avec valgrind (Linux)

# Détecter les fuites mémoire
valgrind --leak-check=full target/debug/mini_sheet

# Profiler la mémoire
valgrind --tool=massif target/debug/mini_sheet
ms_print massif.out.*

Checklist de Débogage

Avant de Commencer

  • Reproduire le problème de manière fiable
  • Identifier les conditions qui déclenchent le problème
  • Vérifier si le problème existe dans les builds debug et release
  • Vérifier les logs existants

Pendant le Débogage

  • Ajouter des points de log stratégiques
  • Utiliser un débogueur pour suivre l'exécution
  • Vérifier les valeurs des variables aux points critiques
  • Tester avec des données minimales (reproduction simplifiée)
  • Vérifier les invariants (assertions)

Après le Débogage

  • Retirer les logs de débogage temporaires
  • Ajouter des tests pour éviter la régression
  • Documenter le problème et la solution
  • Vérifier que la solution n'introduit pas de nouveaux problèmes

FAQ

Q: Comment déboguer un panic ?

R:

  1. Activer les backtraces : RUST_BACKTRACE=1 cargo run
  2. Utiliser un débogueur pour voir la pile d'appels
  3. Vérifier les unwrap() et expect() qui pourraient paniquer

Q: Comment déboguer un problème qui n'apparaît qu'en release ?

R:

  1. Vérifier les optimisations qui pourraient masquer le problème
  2. Utiliser #[cfg(debug_assertions)] pour garder certaines vérifications
  3. Compiler avec -O0 pour désactiver les optimisations

Q: Comment déboguer un problème de performance ?

R:

  1. Utiliser Instant::now() pour mesurer le temps
  2. Profiler avec perf ou valgrind
  3. Identifier les goulots d'étranglement
  4. Optimiser les parties les plus lentes

Q: Comment déboguer un problème de mémoire ?

R:

  1. Utiliser valgrind pour détecter les fuites
  2. Vérifier les allocations avec dbg! ou un profiler
  3. Utiliser RUSTFLAGS="-Z sanitizer=address" pour détecter les erreurs de mémoire

Q: Comment déboguer un problème multi-thread (si ajouté plus tard) ?

R:

  1. Utiliser RUSTFLAGS="-Z sanitizer=thread" pour détecter les data races
  2. Ajouter des logs avec des timestamps
  3. Utiliser des outils comme helgrind (valgrind)

Résumé

Ce guide couvre les techniques essentielles pour déboguer MiniSheet :

  • Outils : println, dbg!, GDB, VS Code, logging
  • Formules : Parsing, évaluation, erreurs de syntaxe
  • UI : Rendu, événements, sélection
  • Erreurs : Sérialisation, désérialisation
  • Performance : Timing, profiling
  • Techniques avancées : Assertions, tracing, profiling

Rappel important : Toujours retirer les logs de débogage avant de commiter, ou les garder conditionnels avec #[cfg(debug_assertions)].


Dernière mise à jour : 2026-01-20