cat swift/giudizio-swift-forma-giusta.md

Lezione 15 35 min

Giudizio Swift: scegliere la forma giusta

Swift idiomatico non significa usare feature avanzate: significa scegliere la forma più chiara per il problema.

Scrivere Swift idiomatico non vuol dire riempire il codice di protocol, extension, map, generics e operatori compatti. Vuol dire usare il linguaggio in modo che il dominio risulti più chiaro, più sicuro è più facile da modificare.

Il punto centrale di questo modulo è il giudizio.

Un codice può compilare, passare i test e comunque non essere un buon codice Swift. Magari funziona, ma:

  • nasconde stati del dominio;
  • usa stringhe dove servono enum;
  • usa classi dove bastano valori;
  • crea protocolli senza bisogno;
  • comprime troppa logica in una closure;
  • espone dettagli che dovrebbero restare privati;
  • usa nomi generici che non spiegano l’intenzione.

Swift idiomatico non è “Swift elegante”. E Swift che comunica bene.

La domanda guida

Quando scegli una forma, chiediti:

Quale versione rende più difficile scrivere codice sbagliato?

Non sempre la versione più breve è quella migliore.

let s = p.filter { $0.q > 0 }.map { $0.v }.reduce(0, +)

Questo codice è compatto, ma non dice quasi nulla.

let righeValide = righe.filter { riga in
  riga.quantita.valore > 0
}

let subtotali = righeValide.map { riga in
  riga.subtotaleInCent
}

let totale = subtotali.reduce(0, +)

È più lungo, ma racconta il dominio.

Idiomatico non significa sempre funzionale

Swift supporta uno stile funzionale, ma non è obbligatorio trasformare ogni ciclo in una catena.

Questo è leggibile:

let totale = righe.reduce(0) { totale, riga in
  totale + riga.subtotaleInCent
}

Questo potrebbe esserlo meno:

let totale = righe.map(\.subtotaleInCent).reduce(0, +)

La seconda versione è corretta. Ma se chi legge il corso sta ancora imparando a riconoscere il dominio, la prima può essere più esplicita.

Il codice idiomatico è quello che bilancia densità e chiarezza.

Partire da tipi semplici

In Swift, una buona scelta iniziale è spesso:

  • struct per dati;
  • enum per alternative;
  • let per stato stabile;
  • funzioni piccole per trasformazioni;
  • protocolli solo quando esiste variabilità reale.

Esempio:

struct RigaCarrello {
  let prodotto: Prodotto
  let quantita: Quantita

  var subtotale: Prezzo {
    prodotto.prezzo.moltiplicato(per: quantita)
  }
}

Questo codice non è spettacolare, ma è idiomatico: il comportamento vive vicino ai dati, i nomi sono di dominio, la proprietà calcolata comunica intenzione.

Evitare astrazioni ornamentali

Questo è un odore:

protocol ProductRepresentable {
  var productName: String { get }
  var productPrice: Int { get }
}

struct Product: ProductRepresentable {
  let productName: String
  let productPrice: Int
}

Il protocollo non aggiunge una capacità reale. Sembra architettura, ma è solo rumore.

Versione più Swift:

struct Prodotto {
  let nome: String
  let prezzo: Prezzo
}

Un tipo concreto, chiaro, testabile.

Preferire nomi del dominio

In Swift i nomi contano molto. Un buon nome riduce commenti e rende le API leggibili.

Poco chiaro:

func calc(_ c: Cart, _ d: Int) -> Int

Meglio:

func totaleApplicandoSconto(_ sconto: Sconto, a carrello: Carrello) -> Prezzo

Il nome non deve essere corto. Deve essere preciso.

Quando una feature è giustificata

Una feature Swift è giustificata quando:

  • elimina uno stato invalido;
  • rende un contratto più chiaro;
  • riduce duplicazione reale;
  • migliora la testabilità;
  • avvicina il codice al linguaggio del dominio;
  • rende più facile cambiare il comportamento.

Non è giustificata solo perché “Swift si scrive così”.

Esempio di refactor mentale

Versione fragile:

func apply(type: String, value: Int, total: Int) -> Int {
  if type == "percent" {
    return total - (total * value / 100)
  }

  if type == "fixed" {
    return total - value
  }

  return total
}

Versione più idiomatica:

enum Sconto {
  case percentuale(Int)
  case importoFisso(cent: Int)
  case nessuno
}

func totaleApplicando(_ sconto: Sconto, a totale: Int) -> Int {
  switch sconto {
  case .percentuale(let valore):
    return totale - (totale * valore / 100)
  case .importoFisso(let cent):
    return totale - cent
  case .nessuno:
    return totale
  }
}

Abbiamo migliorato:

  • nomi;
  • stati validi;
  • eliminazione di stringhe magiche;
  • switch esaustivo;
  • leggibilità.

Checklist

Quando rivedi codice Swift, chiediti:

  • il tipo scelto rappresenta bene il concetto?
  • sto usando struct dove basta un valore?
  • sto usando class solo quando serve identità?
  • gli enum eliminano stringhe o flag fragili?
  • i protocolli descrivono capacità reali?
  • i nomi leggono come dominio?
  • la compattezza sta aiutando o nascondendo?

Esercizio

Prendi una funzione scritta in modo procedurale, con stringhe e if, e trasformala in:

  • enum per gli stati;
  • struct per i dati;
  • funzione con nome esplicito;
  • switch esaustivo;
  • nessun valore magico non nominato.

Poi confronta le due versioni: quale spiega meglio il problema senza commenti?