cat swift/debugging-errori-breakpoint-strumenti.md

Lezione 43 45 min

Debugging: errori, breakpoint e strumenti

Debuggare Swift significa osservare stato, flusso asincrono, errori e memoria con metodo, non aggiungere print a caso.

Debuggare non è indovinare. È costruire una spiegazione del comportamento del programma usando strumenti.

In Swift userai:

  • messaggi del compilatore;
  • breakpoint;
  • debugger LLDB;
  • print mirati;
  • test;
  • Instruments;
  • log;
  • diagnostiche di concurrency e memoria.

Prima il compilatore

Swift spesso ti ferma prima del runtime.

Messaggi su:

  • optional non gestiti;
  • access control;
  • Sendable;
  • actor isolation;
  • tipi incompatibili;
  • try mancante;
  • await mancante.

Non trattarli come ostacoli. Sono feedback sul modello.

Breakpoint

Un breakpoint ferma il programma in un punto.

Usalo per rispondere a domande:

  • questa funzione viene chiamata?
  • con quali parametri?
  • quante volte?
  • quale ramo dello switch viene preso?
  • quale valore è nil?

Non mettere breakpoint casuali. Formula un’ipotesi.

Conditional breakpoint

Se un ciclo gira mille volte, ferma solo il caso interessante:

riga.quantita == 0

Oppure:

coupon.codice == "WELCOME10"

Questo rende il debug più rapido.

LLDB essenziale

Nel debugger puoi usare comandi come:

po variabile
p variabile
bt
  • po: print object, utile per descrizioni leggibili;
  • p: stampa espressione;
  • bt: backtrace, cioè pila delle chiamate.

Non serve diventare esperti subito, ma questi tre comandi aiutano molto.

Debug async

Con async/await, il flusso può sospendersi. Chiediti:

  • dove sono gli await?
  • quale task sta eseguendo?
  • il task è stato cancellato?
  • il catch sta ingoiando l’errore?
  • l’aggiornamento UI avviene sul MainActor?

Nei test e nei log, includi informazioni utili:

print("Loading product \(id)")

Meglio di:

print("here")

Debug di errori

Non trasformare errori in stringhe troppo presto.

catch {
  self.errore = String(describing: error)
}

Va bene al confine UI, ma nel dominio mantieni errori tipizzati.

catch let errore as ErroreCoupon {
  gestisci(errore)
}

Gli errori tipizzati sono più debuggabili.

Debug memoria

Per memory leak:

  • aggiungi temporaneamente deinit;
  • usa Memory Graph in Xcode;
  • usa Instruments per casi seri;
  • cerca closure che catturano self;
  • cerca riferimenti forti reciproci.
deinit {
  print("ProductViewModel deinit")
}

Se non appare, chiediti chi mantiene vivo l’oggetto.

Logging

print è utile in laboratorio. In codice professionale, valuta logging strutturato.

Un log utile contiene:

  • evento;
  • id;
  • stato;
  • errore;
  • contesto minimo.

Evita log rumorosi che non aiutano a decidere.

Checklist debug

Quando hai un bug:

  1. riproducilo;
  2. formula ipotesi;
  3. scegli un punto di osservazione;
  4. usa breakpoint o test;
  5. riduci il caso;
  6. correggi;
  7. aggiungi test se il bug poteva regredire.

Esercizio

Prendi il laboratorio del client API concorrente e inserisci un bug:

  • isLoading resta true dopo cancellazione;
  • un errore viene mostrato anche se è CancellationError;
  • un coupon non valido ritorna totale invariato senza errore.

Debugga con breakpoint o test. Poi scrivi una breve nota: qual era l’ipotesi e come l’hai verificata?