modulo 04 / lezione 22

Isolare la causa

Ricerca binaria nel codice e nei commit

Tagliare lo spazio di ricerca con confronti a metà, diff e cronologia Git. Usare git bisect interattivo e git bisect run con uno script di verifica.

65 min Debugging

In parole semplici

Tra il primo commit “funzionava” e l’ultimo “non funziona più”, c’è una storia di N commit. Trovare quale ha introdotto il bug a mano significa controllarli a uno a uno. La ricerca binaria li controlla in log₂(N) passi: 1024 commit in 10 passi, 1.000.000 in 20.

Git mette a disposizione git bisect, automatizzabile con uno script. È lo strumento più potente del modulo “isolare”.

Parole nuove

  • bisezione: tecnica di ricerca che dimezza lo spazio ad ogni passo.
  • bug good / bad: commit in cui il bug non c’è (good) o c’è (bad).
  • bisect run: modalità automatica di git bisect con uno script di test.
  • commit incolpato: commit identificato come responsabile.

Cosa impari

  • Eseguire una git bisect manuale a fini didattici.
  • Scrivere uno script che git bisect run può usare in automatico.
  • Risparmiare ore su bug nascosti tra molti commit.

Lo schema della bisezione

Hai due punti certi:

  • Commit A: il bug non c’è.
  • Commit B: il bug c’è.

In mezzo c’è la sequenza dei commit. Procedura:

  1. Vai al commit a metà strada (M).
  2. Verifica se il bug c’è.
    • Sì → la responsabilità è tra A (escluso) e M.
    • No → la responsabilità è tra M (escluso) e B.
  3. Ripeti sul nuovo intervallo.

Dopo k passi, lo spazio è ridotto di un fattore 2^k. 16 commit → 4 passi. 1000 → ~10.

git bisect manuale

Procedura:

git bisect start
git bisect bad                # HEAD corrente è "bad"
git bisect good <commit-A>    # commit "buono" noto

Git ti porta automaticamente al commit di mezzo. Tu provi:

# costruisci, esegui, controlla il bug
git bisect good   # se bug NON presente
# oppure
git bisect bad    # se bug presente

Git ti porta al prossimo punto di mezzo. Vai avanti fino a:

<commit-XYZ> is the first bad commit

Per chiudere:

git bisect reset

git bisect automatico (bisect run)

Per gli amanti dell’efficienza: uno script che ritorna 0 se “good” e diverso da 0 se “bad”. Git ci itera.

git bisect start
git bisect bad
git bisect good <commit-A>
git bisect run ./check-bug.sh

Esempio di check-bug.sh:

#!/bin/bash
set -e

npm install --silent
npm run build --silent

# eseguo il test che fallisce solo se il bug è presente
node ./tests/regression-142.js

Se il test esce con 0, Git marca il commit “good”. Se esce con 1, “bad”. Se esce con 125, “skip” (commit non testabile, es. non compila).

Risultato: in dieci minuti Git trova il colpevole tra 200 commit, senza che tu tocchi tastiera.

Esempio realistico

Scenario: regressione nel carrello, scoperta venerdì. Sai che martedì funzionava. In mezzo 47 commit.

git bisect start
git bisect bad HEAD
git bisect good v2.3.0    # tag martedì

Git porta al commit #24. Provi: il bug c’è. git bisect bad. Va al #12. Provi: non c’è. git bisect good. Va al #18. Provi: c’è. E così via.

Sei passaggi dopo:

commit abc1234 is the first bad commit
Author: ...
Date:   ...
    Refactor carrello.calculateTotal

Da qui hai il commit. Il fix può essere git revert abc1234 (modulo 5) oppure un’indagine mirata su quelle 30 righe.

Saltare commit non testabili

A volte un commit in mezzo non compila (refactor parziale). Marcalo skip:

git bisect skip

Git lo evita e prova il vicino.

Non solo git bisect: bisezione nel codice

La stessa logica si applica a:

  • File grande: commenta metà, vedi se il bug resta.
  • Dati grandi: usa metà dei record, vedi se il bug resta.
  • Pipeline: spegni metà degli step.

git bisect è l’applicazione a Git di un’idea generale.

Esercizio guidato

In una repo didattica (anche un piccolo progetto tuo):

  1. Aggiungi 8 commit con piccole modifiche, in uno introduci di proposito un bug.
  2. Verifica che HEAD è “bad” e che il primo commit è “good”.
  3. Esegui git bisect start + bad + good. Lascia che Git ti porti ai commit. Per ogni commit prova manualmente.
  4. Identifica il commit colpevole.
  5. Ripeti l’esercizio scrivendo uno script check-bug.sh e usando git bisect run.

Quando NON usare bisect

  • Bug non riproducibile (modulo 1): bisect ha bisogno di test deterministico.
  • Bug nuovo (mai esistita versione “good”).
  • Commit storia “appiattita” o ribasata che ha rimosso la cronologia rilevante.

Errori frequenti del principiante

  • Dimenticare git bisect reset alla fine. Resti in stato “detached HEAD” e confondi i prossimi push.
  • Usare bisect con bug flaky. I “false good” portano fuori strada. Stabilizza prima.
  • Marcare bad/good a memoria. Verifica sempre, anche se sembra “ovvio”.
  • Saltare lo script automatico. Un’ora di scripting risparmia tre ore di click.