cat swift/giudizio-swift-forma-giusta.md
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:
structper dati;enumper alternative;letper 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
structdove basta un valore? - sto usando
classsolo 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?