Jak nasazujeme funkce, které se dotýkají peněz, aniž bychom někomu omylem změnili cenu
Chyba v cenách je ta, kterou už nevezmete zpět, jakmile se strhne platba z karty. Tady je kázeň, se kterou přidáváme do rezervačního procesu slevy, ceny podle personálu a akce, aniž by se jediná cena pohnula dřív, než chceme.
Většina chyb vás stojí omluvu. Chyba v cenách vás stojí vratku, chargeback a klienta, který už nevěří číslu na obrazovce. Sázky jsou nevyrovnané, takže postup okolo všeho, co se dotýká peněz, je záměrně pomalejší než zbytek naší práce. Takhle vypadá, a jako příklad použijeme nedávnou funkci: slevu na celou kategorii, kdy salon může dát do slevy všechna ošetření pleti naráz.
Vypnuto, v produkci, nedělá nic
První řádek funkce je přepínač. Čte proměnnou prostředí a výchozí stav je vypnuto. To znamená, že můžeme kód sloučit a nasadit dlouho předtím, než začne cokoli dělat, a každá cena zůstane bajt po bajtu stejná jako den předtím. Není tu žádné velké spuštění, ze kterého by šel strach, protože spuštění je samostatný nudný krok, který uděláme, až si budeme jistí.
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);}Jedna funkce, jeden úkol
Aritmetika slevy žije v jediné čisté funkci. Bere haléře a procenta a vrací haléře. Nemá databázi, nemá požadavek a nemá hodiny, což znamená, že je triviální otestovat každý její okraj izolovaně: nulová sleva, běžná sleva, omezení nahoře, záporná hodnota, která by nikdy neměla nastat. Když je výpočet jedna malá funkce, kterou udržíte v hlavě, přestanete o ní hádat.
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);}Přepočítat na serveru, vždycky
Prohlížeč ukazuje cenu, aby klient věděl, kolik zaplatí, ale to číslo je vždy jen zobrazení. Když přijde rezervace, server načte skutečnou službu, vyhodnotí cenu podle personálu, uplatní případnou aktivní slevu na kategorii a pak spočítá slevu, a to všechno z dat, která si sám stáhl. Když někdo požadavek cestou upravil, nezmění to nic, protože cena stržená z karty nikdy nebyla cenou, kterou poslal prohlížeč. Tohle je nejdůležitější návyk v kódu okolo peněz: hranice důvěry je server a součet dělá server.
Zkuste to rozbít dřív než zákazník
Procházející testy vám řeknou, že případy, na které jste mysleli, fungují. Neřeknou vám nic o případu, který vám unikl. Než tedy cenový přepínač zapneme, projedeme nepřátelské kolo, jehož jediným úkolem je najít způsob, jak peníze pokazit. Jednotlivé kontroly berou věc z různých stran: jedna hledá jakoukoli cestu, která klientovi naúčtuje příliš, druhá cestu, kterou podnik přichází o peníze, třetí ověřuje, že je funkce s vypnutým přepínačem opravdu netečná, a čtvrtá jde po zaokrouhlování a driftu na hranicích. Sleva navršená na kupon a na zálohu je přesně to místo, kde se schová zlomek haléře, takže právě tam se díváme nejpozorněji.
Nic z toho není chytré. Je to přepínač, malá funkce, server, který si počítá sám, a zvyk zkoušet si rozbít vlastní práci dřív, než se nasadí. Dohromady je to rozdíl mezi tím přidat funkci, která se dotýká peněz, a nespat kvůli ní.