cat swift/api-design-nomi-firme-leggibilita.md

Lezione 2050 min

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.