Come rilasciamo funzioni che toccano i soldi senza cambiare per sbaglio il prezzo a nessuno
Un bug sui prezzi è di quelli che non puoi più ritirare una volta che una carta è stata addebitata. Ecco la disciplina che usiamo per aggiungere sconti, prezzi per operatore e promozioni al flusso di prenotazione senza che un solo prezzo si muova prima che lo vogliamo noi.
Quasi tutti i bug ti costano delle scuse. Un bug sui prezzi ti costa un rimborso, uno storno e un cliente che non si fida più del numero sullo schermo. Il rischio è sbilanciato, quindi il processo intorno a tutto quello che tocca i soldi è volutamente più lento del resto del nostro lavoro. Ecco com'è fatto, prendendo come esempio una funzione recente: l'offerta su un'intera categoria, dove un salone può mettere in promozione tutti i trattamenti viso in una volta sola.
Spento di default, in produzione, senza fare niente
La prima riga della funzione è un flag. Legge una variabile d'ambiente e per impostazione predefinita è spento. Vuol dire che possiamo fare merge e deploy del codice molto prima che faccia qualcosa, e ogni prezzo resta identico byte per byte a quello del giorno prima. Non c'è nessun lancio in grande stile per cui essere nervosi, perché il lancio è un passaggio separato e noioso che facciamo quando siamo sicuri.
export const FEATURE_LIVE = process.env.FEATURE_LIVE === "true"; // Read at the one place it matters. When the flag is off, the branch// is skipped entirely: no extra query, no price change, nothing.if (FEATURE_LIVE) { const pct = await activeCategorySalePercent(orgId, "service", service.category); if (pct > 0) fullPriceCents = applyCategorySaleCents(fullPriceCents, pct);}Una funzione, un compito
L'aritmetica dello sconto sta in una singola funzione pura. Prende centesimi e una percentuale, e restituisce centesimi. Non ha database, non ha richieste e non ha orologio, il che vuol dire che è banale testarne ogni caso limite in isolamento: un'offerta a zero, un'offerta normale, un tetto massimo, un negativo che non dovrebbe mai capitare. Quando la matematica è una piccola funzione che ti sta in testa, smetti di andare a intuito.
export function applyCategorySaleCents( baseCents: number, salePercent: number | null | undefined,): number { const base = cents(baseCents); const pct = Math.max(0, Math.min(100, Math.round(salePercent ?? 0))); if (pct === 0) return base; return cents((base * (100 - pct)) / 100);}Ricalcola sul server, sempre
Il browser mostra un prezzo così il cliente sa quanto pagherà, ma quel numero è solo una visualizzazione. Quando arriva una prenotazione, il server carica il servizio vero, risolve il prezzo dell'operatore, applica l'eventuale offerta di categoria attiva e poi calcola lo sconto, tutto a partire da dati che ha recuperato da solo. Se qualcuno ha modificato la richiesta per strada, non cambia niente, perché il prezzo addebitato sulla carta non è mai stato il prezzo mandato dal browser. Questa è l'abitudine più importante in assoluto nel codice che tocca i soldi: il confine di fiducia è il server, ed è il server che fa la somma.
Prova a romperlo prima che lo faccia un cliente
I test che passano ti dicono che i casi a cui hai pensato funzionano. Non ti dicono niente del caso che ti sei perso. Quindi prima di accendere un flag sui prezzi facciamo un giro ostile il cui unico compito è trovare un modo di far sbagliare i conti. Controlli separati prendono ciascuno un'angolazione diversa: uno va a caccia di percorsi che addebitano troppo al cliente, uno cerca un percorso che fa perdere soldi all'attività, uno verifica che la funzione sia davvero inerte quando il flag è spento, e uno se la prende con gli arrotondamenti e gli scarti ai valori limite. Un'offerta sommata a un coupon sommato a un acconto è esattamente il punto dove si nasconde una frazione di centesimo, quindi è lì che guardiamo con più attenzione.
Niente di tutto questo è geniale. È un flag, una piccola funzione, un server che fa i conti per conto suo, e l'abitudine di provare a rompere il proprio lavoro prima di rilasciarlo. Messi insieme, sono la differenza tra aggiungere una funzione che tocca i soldi e perderci il sonno.