- Introduction
- Outils de Débogage
- Débogage des Formules
- Débogage de l'Interface Utilisateur
- Débogage des Erreurs
- Débogage des Performances
- Cas d'Usage Spécifiques
- Techniques Avancées
- Checklist de Débogage
- FAQ
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.
- 🔴 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
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
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 typeExemple 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
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
Installation :
# Windows (via MSYS2/MinGW)
pacman -S mingw-w64-x86_64-gdb
# Linux
sudo apt-get install gdb
# macOS
brew install gdbUtilisation 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 # QuitterExemple 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) continueConfiguration .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 :
- Placer un breakpoint (clic gauche dans la marge)
- Appuyer sur F5 pour démarrer le débogage
- Utiliser les contrôles (Continue, Step Over, Step Into, Step Out)
- Inspecter les variables dans le panneau "Variables"
- Utiliser la console de débogage pour évaluer des expressions
Raccourcis clavier :
F5: Démarrer/ContinuerF10: Step OverF11: Step IntoShift+F11: Step OutShift+F5: Arrêter
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 runNiveaux de log :
trace!: Très détaillé (tout)debug!: Informations de débogageinfo!: Informations généraleswarn!: Avertissementserror!: Erreurs
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 :
- ✅ La formule commence-t-elle par
=? - ✅ La formule est-elle stockée correctement dans
Sheet::raw? - ✅
eval_cellest-il appelé ? - ✅ Y a-t-il une erreur de parsing ?
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 :
- ✅ Parenthèses équilibrées ?
- ✅ Opérateurs valides ?
- ✅ Références de cellules correctes ?
- ✅ Arguments de fonctions corrects ?
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 :
- ✅ La cellule s'auto-référence-t-elle ?
- ✅ Y a-t-il une chaîne de dépendances qui boucle ?
- ✅ Le
stackest-il correctement géré (insert/remove) ?
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 :
- ✅ Le nom de la fonction est-il en majuscules ?
- ✅ Y a-t-il des espaces ou caractères invisibles ?
- ✅ La fonction est-elle bien dans le
match?
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 :
- ✅ Les opérandes sont-ils correctement parsés ?
- ✅ L'ordre des opérations est-il respecté ?
- ✅ Les parenthèses sont-elles correctement gérées ?
- ✅ Les références de cellules sont-elles correctement résolues ?
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 :
- ✅
get_cellretourne-t-il la bonne valeur ? - ✅ Le rectangle de rendu est-il correct ?
- ✅ La cellule est-elle dans la zone visible ?
- ✅ Y a-t-il un problème de cache ?
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 :
- ✅ L'événement de clic est-il capturé ?
- ✅ Les coordonnées sont-elles correctes ?
- ✅ Les modificateurs (Shift, Ctrl) sont-ils détectés ?
- ✅ La sélection est-elle mise à jour dans
self.selection?
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 :
- ✅ Les événements clavier sont-ils capturés ?
- ✅ Les modificateurs sont-ils détectés ?
- ✅ Y a-t-il un conflit avec d'autres raccourcis ?
- ✅ Le focus est-il sur la bonne fenêtre ?
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 :
- ✅ Toutes les structures sont-elles
Serialize? - ✅ Y a-t-il des valeurs
NaNouInfinitydans les nombres ? - ✅ Les
HashMapavec clés(usize, usize)utilisent-elles le module de sérialisation personnalisé ? - ✅ Y a-t-il des références circulaires dans les données ?
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 :
- ✅ Le fichier est-il un JSON valide ?
- ✅ Le schéma correspond-il à la structure
Sheet? - ✅ Les modules de désérialisation personnalisés sont-ils corrects ?
- ✅ Y a-t-il des valeurs invalides (NaN, Infinity) ?
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 :
- ✅ Implémenter le recalcul incrémental (seulement les cellules affectées)
- ✅ Utiliser un cache plus efficace
- ✅ Paralléliser le recalcul (si possible)
- ✅ Éviter les recalculs inutiles
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 :
- ✅ Virtualiser le rendu (seulement les cellules visibles)
- ✅ Réduire les allocations dans la boucle de rendu
- ✅ Utiliser le caching pour les cellules statiques
- ✅ Optimiser les calculs de position
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)
}
// ...
}
}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);
}
}
}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);
}
}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);
// ...
}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)
}# Compiler en mode release avec symboles
RUSTFLAGS="-g" cargo build --release
# Profiler
perf record -g target/release/mini_sheet
# Analyser
perf report# 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.*- 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
- 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)
- 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
R:
- Activer les backtraces :
RUST_BACKTRACE=1 cargo run - Utiliser un débogueur pour voir la pile d'appels
- Vérifier les
unwrap()etexpect()qui pourraient paniquer
R:
- Vérifier les optimisations qui pourraient masquer le problème
- Utiliser
#[cfg(debug_assertions)]pour garder certaines vérifications - Compiler avec
-O0pour désactiver les optimisations
R:
- Utiliser
Instant::now()pour mesurer le temps - Profiler avec
perfouvalgrind - Identifier les goulots d'étranglement
- Optimiser les parties les plus lentes
R:
- Utiliser
valgrindpour détecter les fuites - Vérifier les allocations avec
dbg!ou un profiler - Utiliser
RUSTFLAGS="-Z sanitizer=address"pour détecter les erreurs de mémoire
R:
- Utiliser
RUSTFLAGS="-Z sanitizer=thread"pour détecter les data races - Ajouter des logs avec des timestamps
- Utiliser des outils comme
helgrind(valgrind)
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