cat swift/optionals-modello-di-assenza.md

Lezione 0440 min

Optionals come modello di assenza

Gli optionals non sono una seccatura sintattica: sono il modo con cui Swift rende visibile la possibilità che un dato manchi.

Gli optionals sono uno dei primi punti in cui Swift sembra più severo di altri linguaggi. In realtà sono uno dei suoi strumenti più importanti.

Un optional non significa “qui c’è un problema”. Significa: questo dato può essere assente, e il codice deve tenerne conto.

let email: String? = nil

Il tipo String? dice qualcosa che una semplice String non dice: l’email potrebbe non esserci.

Assenza come informazione

In un dominio reale, l’assenza può avere significati diversi.

Un utente potrebbe non avere ancora verificato l’email. Un prodotto potrebbe non avere uno sconto. Un ordine potrebbe non avere ancora un codice di tracciamento.

struct Utente {
  let nome: String
  let emailVerificata: String?
}

struct Prodotto {
  let nome: String
  let scontoPercentuale: Int?
}

struct Ordine {
  let codice: String
  let trackingCode: String?
}

In tutti questi casi nil non è un errore tecnico. È uno stato possibile del dominio.

Optional non è una stringa vuota

Un errore molto comune è usare valori finti per evitare gli optionals:

let trackingCode = ""

Ma una stringa vuota può significare molte cose:

  • non è ancora disponibile;
  • è stato cancellato;
  • è arrivato vuoto da una API;
  • qualcuno ha dimenticato di valorizzarlo.

Con un optional il significato è più chiaro:

let trackingCode: String? = nil

Assente significa assente.

Unwrapping con if let

Per usare il valore dentro un optional devi prima gestire il caso in cui manchi.

let trackingCode: String? = "IT123"

if let codice = trackingCode {
  print("Traccia ordine: \(codice)")
} else {
  print("Tracking non ancora disponibile")
}

if let crea una costante non optional solo nel ramo in cui il valore esiste.

guard let: uscire presto

Quando l’assenza impedisce di continuare, guard let rende il flusso più leggibile.

func inviaEmailConferma(a email: String?) {
  guard let email else {
    print("Impossibile inviare: email mancante")
    return
  }

  print("Invio conferma a \(email)")
}

Dopo il guard, email è una String, non più una String?.

Questa forma è molto usata per validare dati in ingresso.

Optional chaining

Quando vuoi accedere a proprietà o metodi solo se il valore esiste, puoi usare ?..

struct Profilo {
  let bio: String?
}

struct Utente {
  let profilo: Profilo?
}

let utente = Utente(profilo: Profilo(bio: "Sviluppatrice Swift"))
let lunghezzaBio = utente.profilo?.bio?.count

Il tipo di lunghezzaBio è Int?, perché ogni passaggio potrebbe fallire.

Nil coalescing

Quando hai un valore di fallback sensato, usa ??.

let nomeVisualizzato = utente.profilo?.bio ?? "Nessuna bio disponibile"

Attenzione: il fallback deve essere davvero corretto per il dominio. Non usarlo per nascondere un caso che invece andrebbe gestito esplicitamente.

Force unwrap: quasi sempre un odore

Swift permette di forzare l’estrazione:

let email: String? = nil
print(email!)

Se email è nil, il programma va in crash.

Il force unwrap ! non è vietato in assoluto, ma dovrebbe essere raro. Se lo usi spesso, probabilmente stai dicendo al compilatore: “fidati di me”, proprio nel punto in cui Swift ti stava chiedendo di essere preciso.

Optional o enum?

Un optional è perfetto quando ci sono due stati:

  • c’è un valore;
  • non c’è un valore.

Ma se gli stati sono più ricchi, un enum è spesso migliore.

Versione troppo povera:

let pagamentoCompletato: Bool?

Cosa significa nil? Non iniziato? In attesa? Errore?

Meglio:

enum StatoPagamento {
  case nonIniziato
  case inAttesa
  case completato
  case fallito(motivo: String)
}

Gli optionals non devono diventare un posto dove nascondere stati che meritano un nome.

Esempio nel carrello

struct Coupon {
  let codice: String
  let scontoPercentuale: Int
}

struct Carrello {
  let prodotti: [Prodotto]
  let coupon: Coupon?

  var descrizioneCoupon: String {
    guard let coupon else {
      return "Nessun coupon applicato"
    }

    return "Coupon \(coupon.codice): -\(coupon.scontoPercentuale)%"
  }
}

Qui coupon è optional perché un carrello può legittimamente non avere un coupon.

Checklist

Usa un optional quando:

  • l’assenza è uno stato valido;
  • ci sono solo due possibilità: valore presente o valore assente;
  • il chiamante deve essere costretto a gestire l’assenza.

Evita un optional quando:

  • stai nascondendo più stati diversi dentro nil;
  • hai un valore di default che è sempre corretto;
  • l’assenza dovrebbe essere un errore;
  • stai usando ! per aggirare il compilatore.

Esercizio

Estendi il dominio carrello con:

  • un Utente con email opzionale;
  • un Coupon opzionale nel carrello;
  • un Ordine con tracking opzionale.

Poi scrivi tre funzioni:

func descriviEmail(_ utente: Utente) -> String
func descriviCoupon(_ carrello: Carrello) -> String
func descriviTracking(_ ordine: Ordine) -> String

Ogni funzione deve gestire esplicitamente il caso di assenza senza usare !.