Contesto
Il processo di debug del “Bug del Point & Figure” ha evidenziato una debolezza nel nostro workflow: il debugging era un processo lento, basato su intuizioni e tentativi manuali. Per risolvere questa inefficienza e capitalizzare l’apprendimento, abbiamo formalizzato il nostro approccio nel documento QC DD-DEBUGGING-PROTOCOL.md.
Questo modulo, debugging_tools.py, è l’implementazione pratica di quel protocollo. Fornisce un arsenale di strumenti riutilizzabili progettati per accelerare la diagnosi dei bug e rendere il nostro codice più robusto.
Decisione
Abbiamo creato un modulo centralizzato, fire/utils/debugging_tools.py, che fornisce un set di utility di debugging pronte all’uso.
Questi strumenti sono progettati per essere applicati durante lo sviluppo e la diagnosi, e includono:
- Decorator Avanzati: Per tracciare l’esecuzione delle funzioni (
@debug_trace) e misurarne le performance (@log_execution_time). - Funzioni di Validazione: Per verificare che i dati in ingresso a un componente (es.
DataFrame, parametri) siano corretti prima dell’esecuzione (validate_params,validate_dataframe). - Una Classe
Comparator: Per confrontare “side-by-side” gli output di due versioni di codice, fondamentale per individuare regressioni. - Context Manager: Per profilare e delimitare blocchi di codice specifici in modo pulito ed elegante.
Conseguenze
Positive
- Diagnosi Accelerata: Gli strumenti permettono di applicare il “Protocollo di Debug a 5 Livelli” in modo rapido e standardizzato, riducendo drasticamente il tempo necessario per trovare la causa radice di un bug.
- Codice più Robusto: L’uso sistematico dei validatori previene interi set di bug, intercettando dati malformati all’ingresso di un componente, non a metà della sua esecuzione.
- Migliore Developer Experience (DX): Fornisce un linguaggio e strumenti comuni per il debugging, rendendo la collaborazione più efficiente.
- Prevenzione delle Regressioni: La classe
Comparatorpuò essere usata all’interno di test per garantire che un refactoring non alteri l’output di una funzione.
Negative / Avvertenze
- Impatto sulle Performance: I decorator, specialmente
@debug_trace, introducono un overhead. Non devono essere usati su funzioni critiche per le performance in codice di produzione. - Verbosity dei Log: Un uso eccessivo può rendere i log difficili da leggere. Vanno applicati in modo mirato durante il debug.
Guida Pratica all’Uso (debugging_tools.py)
1. Tracciare Chiamate di Funzione con @debug_trace
Scopo: Applicare il “Livello 1: Verifica Input/Output” del protocollo. Logga automaticamente argomenti, keyword arguments e valore di ritorno di una funzione.
Uso: Decora una funzione critica o un worker.
Esempio nel PointAndFigureWorker:
from fire.utils.debugging_tools import debug_trace
class PointAndFigureWorker(QRunnable):
@debug_trace(max_repr_len=200, log_result=False) # log_result=False per output grandi
def run(self):
# ... la tua logica ...Output nei Log:
╔═══ CALL: PointAndFigureWorker.run
║ args: (<PointAndFigureWorker object at ...>,)
╚═══ RETURN: PointAndFigureWorker.run (25.43ms)2. Validare gli Input all’Ingresso
Scopo: Prevenire bug intercettando dati non validi prima che causino problemi.
Uso: All’inizio di un metodo run o di una funzione che riceve dati complessi.
Esempio nel PointAndFigureWorker:
from fire.utils.debugging_tools import validate_params, validate_dataframe
def run(self):
try:
# 1. Valida il DataFrame
validate_dataframe(
self.ohlc_data,
required_columns=['high', 'low', 'close'],
component_name='PointAndFigureWorker',
min_rows=10
)
# 2. Valida i parametri
validate_params(
self.params,
required=['box_size', 'reversal'],
component_name='PointAndFigureWorker'
)
except ValueError as e:
# Gestisci l'errore di validazione
self.signals.error.emit(str(e))
return
# ... resto della logica, ora sai che gli input sono sicuri3. Confrontare Versioni con Comparator
Scopo: Trovare rapidamente regressioni confrontando l’output di una vecchia versione funzionante con una nuova.
Uso: In uno script di debug isolato (scripts/debug/).
Esempio di confronto:
from fire.utils.debugging_tools import Comparator
# Assumi di avere df_vecchio e df_nuovo
Comparator.compare_dataframes(
df_vecchio,
df_nuovo,
name1="v1.2.0",
name2="v1.9.5"
)Output:
============================================================
CONFRONTO DATAFRAME: v1.2.0 vs v1.9.5
============================================================
Shape: (51, 3) vs (3, 3)
❌ Shape diversi!4. Misurare Performance con timing_context
Scopo: Profilare rapidamente un blocco di codice specifico senza aggiungere boilerplate.
Uso: Avvolgi la sezione di codice che vuoi misurare con un with.
Esempio:
from fire.utils.debugging_tools import timing_context
def run(self):
# ...
with timing_context("P&F Algorithm Calculation"):
pnf_columns = self._calculate_pnf(...)
# ...Output nei Log:
[TIMING] P&F Algorithm Calculation: 45.12ms5. Best Practices per l’Utilizzo
- Non Decorare Tutto: Applica
@debug_tracesolo alle funzioni “cerniera” del tuo sistema o a quelle che sospetti stiano causando problemi. - Usa i Validatori Sistematicamente: I
validate_*sono a basso costo e ad alto rendimento. Usali ai confini dei tuoi componenti (es. nei worker) per garantire la robustezza. - Il
Comparatorè per il Debug, non per la Produzione: Usalo in script dedicati per analizzare le regressioni. - Usa
debug_sectionper Leggibilità: Quando un log di debug diventa complesso, usawith debug_section("Nome Sezione"):per raggruppare e organizzare l’output.