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 Comparator può 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 sicuri

3. 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.12ms

5. Best Practices per l’Utilizzo

  1. Non Decorare Tutto: Applica @debug_trace solo alle funzioni “cerniera” del tuo sistema o a quelle che sospetti stiano causando problemi.
  2. 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.
  3. Il Comparator è per il Debug, non per la Produzione: Usalo in script dedicati per analizzare le regressioni.
  4. Usa debug_section per Leggibilità: Quando un log di debug diventa complesso, usa with debug_section("Nome Sezione"): per raggruppare e organizzare l’output.