cat swift/api-design-nomi-firme-leggibilita.md
API design: nomi, firme e leggibilità
Le API Swift migliori si leggono come frasi: nomi, label dei parametri e tipi devono guidare l'uso corretto.
In Swift, il design delle API è parte del linguaggio. I nomi delle funzioni, le label dei parametri e i tipi di ritorno determinano quanto sarà naturale usare il codice.
Una API buona non richiede di leggere l’implementazione per capire come usarla.
Le funzioni devono leggere come frasi
Poco Swift:
func apply(_ coupon: Coupon, _ cart: Cart) -> Cart
Meglio:
func applica(_ coupon: Coupon, a carrello: Carrello) throws -> Carrello
La chiamata legge bene:
let aggiornato = try engine.applica(coupon, a: carrello)
Il primo parametro spesso completa il nome della funzione; gli altri hanno label che chiariscono il ruolo.
Label esterne
Swift distingue nome interno ed esterno dei parametri.
func totaleApplicando(_ sconto: Sconto, a prezzo: Prezzo) -> Prezzo {
...
}
Chiamata:
totaleApplicando(.percentuale(10), a: prezzo)
Dentro la funzione il parametro si chiama prezzo; fuori la label è a.
Questa possibilità rende le API molto leggibili.
Evitare nomi generici
Evita:
func handle(data: Data)
func process(item: Item)
func update(value: Int)
Chiediti cosa fa davvero.
func decodificaProdotti(da data: Data) throws -> [ProdottoDTO]
func applica(_ sconto: Sconto, a prezzo: Prezzo) -> Prezzo
func aggiornaQuantita(_ quantita: Quantita, per prodotto: Prodotto)
Il nome dovrebbe ridurre l’ambiguità.
Tipi di ritorno significativi
Questa funzione è povera:
func validate(_ coupon: Coupon) -> Bool
Se ritorna false, perché?
Meglio:
func valida(_ coupon: Coupon, nel contesto: ContestoCoupon) -> Result<Coupon, ErroreCoupon>
Oppure:
func valida(_ coupon: Coupon, nel contesto: ContestoCoupon) throws
Scegli il ritorno in base all’informazione che chiama il codice deve avere.
Nomi negativi e booleani
I booleani devono essere leggibili:
let utenteHaGiaOrdinato: Bool
let couponScaduto: Bool
let carrelloVuoto: Bool
Evita doppie negazioni:
if !utente.nonVerificato { ... }
Meglio:
if utente.emailVerificata { ... }
Mutating API
Se un metodo modifica il valore:
mutating func aggiungi(_ riga: RigaCarrello)
Se restituisce un nuovo valore:
func aggiungendo(_ riga: RigaCarrello) -> Carrello
Il nome aiuta a distinguere mutazione e trasformazione.
carrello.aggiungi(riga)
let nuovo = carrello.aggiungendo(riga)
API simmetriche
Quando hai operazioni opposte, usa nomi coerenti:
mutating func applica(_ coupon: Coupon)
mutating func rimuoviCoupon()
Oppure:
func applicando(_ coupon: Coupon) -> Carrello
func rimuovendoCoupon() -> Carrello
La coerenza è più importante della creatività.
Evitare parametri primitivi ambigui
Poco chiaro:
func applicaSconto(_ valore: Int, _ totale: Int) -> Int
Meglio:
func applica(_ sconto: Sconto, a totale: Prezzo) -> Prezzo
I tipi aiutano la firma a spiegarsi.
Errori nella firma
Se una funzione può fallire per motivi di dominio, la firma deve mostrarlo:
func creaOrdine(da carrello: Carrello, per utente: Utente) throws -> Ordine
Non nascondere fallimenti importanti dentro valori di default o stati parziali.
Checklist API
Rivedi una funzione e chiediti:
- la chiamata si legge come una frase?
- le label chiariscono il ruolo dei parametri?
- il primo parametro completa il nome?
- i tipi primitivi potrebbero essere tipi di dominio?
- il fallimento è visibile nella firma?
- il nome distingue mutazione e trasformazione?
- il booleano evita doppie negazioni?
Esercizio
Rinomina queste API:
func calc(_ c: Cart, _ d: Int) -> Int
func validate(_ c: Coupon) -> Bool
func update(_ p: Product, _ q: Int)
func make(_ u: User, _ c: Cart) -> Order?
Obiettivo:
- nomi in italiano coerenti con il resto del corso;
- label leggibili;
- tipi di dominio dove servono;
- errori espliciti quando il fallimento conta.