modulo 05 / lezione 25
Correggere senza regressioni
Fix minimo e fix cosmetico
Separare la correzione necessaria da refactor e pulizie che rischiano di confondere la verifica.
In parole semplici
Quando trovi il bug, le mani prudono. Quel if annidato è brutto, quel nome di variabile è terribile, quella funzione è troppo lunga. Resistere è la prima regola del fix: il fix corregge il bug, niente di più. Tutto il resto va in un altro diff.
Mescolare fix e refactor è il modo più rapido per introdurre regressioni mentre risolvi un bug.
Parole nuove
- fix minimo: la modifica più piccola che corregge il bug e non altro.
- fix cosmetico: modifiche di forma (naming, whitespace, riordino) non strettamente necessarie.
- refactor: ristrutturazione del codice senza cambiarne il comportamento esterno.
- diff: differenza tra due versioni del codice (
git diff).
Cosa impari
- Distinguere ciò che corregge da ciò che ripulisce.
- Dividere un cambiamento misto in due commit puliti.
- Capire perché la disciplina protegge la verificabilità.
La regola della singola intenzione
Ogni diff dovrebbe rispondere a una sola domanda:
- “Cosa corregge questo diff?”
- oppure: “Cosa rinomina/refattora questo diff?”
- oppure: “Cosa aggiunge questo diff?”
Mai due risposte in uno solo. Quando rivedi il diff prima del commit, se devi rispondere “fix più rinomina più ordinamento”, separa.
Perché conta
1. La verifica è ambigua
Se hai cambiato 30 righe (5 fix + 25 cosmetiche) e il test passa, non sai se passa perché il fix funziona o perché qualcosa di cosmetico ha cambiato comportamento per caso.
2. Il revert è più difficile
Il fix dimostra un problema: voglio revertarlo. Ma è mescolato a un refactor utile. Non posso revertare un pezzo solo.
3. La code review affoga
Il revisore deve guardare 30 righe per capire le 5 importanti. Si distrae e approva male.
4. git blame mente
Tra sei mesi, qualcuno fa git blame su una riga importante e trova “rinominato variabile”. Il fix originale è perso nella storia.
Esempio: lo stesso fix in due varianti
Bug: calcolaSconto ritorna NaN quando percentuale è 0.
Variante A: fix minimo
function calcolaSconto(prezzo, percentuale) {
- return prezzo - (prezzo * percentuale / 100);
+ return prezzo - (prezzo * (percentuale || 0) / 100);
}
Una riga. Si vede subito: “abbiamo aggiunto || 0 per gestire null/undefined”.
Variante B: fix + refattorizzazione (sbagliata)
-function calcolaSconto(prezzo, percentuale) {
- return prezzo - (prezzo * percentuale / 100);
+function calcolaSconto(originalPrice, discountPercent = 0) {
+ if (originalPrice <= 0) return 0;
+ const discount = (originalPrice * discountPercent) / 100;
+ return originalPrice - discount;
}
Cinque righe. Tutto sembra un miglioramento. Ma:
- Hai cambiato il nome dei parametri (potresti rompere chi chiama).
- Hai introdotto un controllo
<= 0non richiesto dal bug. - Hai cambiato un’espressione algebricamente equivalente, ma per il lettore è un’altra cosa.
Se domani un test fallisce, qual è il colpevole?
La pratica: due commit, due intenzioni
Per separare:
# 1. fix minimo
git add -p # aggiungi solo le righe del fix
git commit -m "fix(sconto): gestire percentuale null/undefined"
# 2. refactor
git add -p # aggiungi solo le righe del refactor
git commit -m "refactor(sconto): rinominare parametri e ristrutturare guard"
Due commit. Due diff puliti. Due revert possibili.
Quando il refactor è dentro il fix
Eccezione legittima: a volte il bug è un problema strutturale che richiede ristrutturazione minima per essere fixato. Esempio: la funzione legge da uno stato globale che viene aggiornato in ordine sbagliato; per fixare devi passare lo stato come parametro.
In quel caso il refactor è necessario al fix, e va nel commit del fix con un commit message che lo spiega:
fix(carrello): passare cart come parametro a calcolaTotale
Il bug era causato dalla lettura di `cart` globale prima dell'update.
Il fix richiede di passare il cart come parametro esplicito, eliminando
la dipendenza dallo stato globale per quella funzione.
Quando il refactor è invece opportunismo (“già che ci sono”), va separato.
Esercizio guidato
Apri una repo dove hai un piccolo bug da risolvere (o creane uno).
- Risolvi il bug mescolando fix e qualche pulizia. Vedi il diff.
git resete ricomincia. Questa volta:- Prima fix puro.
- Commit.
- Refactor.
- Commit.
- Confronta i due percorsi: quale ti renderebbe più sicuro al merge?
Tecnica: git add -p per separare
git add -p divide le modifiche in hunk e per ognuno chiede se aggiungerlo. Ti permette di costruire un commit con solo certe righe lasciando le altre fuori.
Comandi rapidi:
yaggiunginsaltasdividi ancoraemodifica manualeqesci
Errori frequenti del principiante
- “Già che ci sono”. Ogni “già che ci sono” è una regressione che aspetta.
- Commit “WIP fix + rinomine + cleanup”. Tre intenzioni, nessuna verificabile.
- Refactor senza test. Il refactor sembra “ovvio” ma può cambiare comportamento.
- Saltare
git add -p. Senza, è troppo facile committare tutto in fretta.