2.20

View in English

2.20 Felhantering och motståndskraftsmönster

Översikt och motivation

Varje program du skriver kommer att fallera. En disk fylls, ett nätverk tappar anslutningen, en tjänst får tidsgränsfel, en anropare skickar skräp, ett beroende returnerar något dokumentationen aldrig nämnde. Frågan är aldrig om fel inträffar. Den är om din kod möter felet med en plan eller med en överraskning. Felhantering är hantverket att besluta, rad för rad och funktion för funktion, vad din kod gör när världen inte samarbetar. Det är den minst glamorösa delen av konstruktion och den del som avgör, mer än någon funktion, om människor litar på ditt system.

Det här kapitlet handlar om motståndskraft på kod- och komponentnivå: valen inuti en funktion, en modul eller ett API. Det kompletterar kapitel 3.5, som behandlar motståndskraft på systemnivå (lastbalansering, replikering, failover över tjänster). Kapitel 3.5 håller hela plattformen uppe när en region slocknar. Det här kapitlet hindrar en enskild begäran från att korrumpera dina data eller försvinna spårlöst. De två förstärker varandra. En kretsbrytare i din arkitektur betyder lite om koden bakom den sväljer undantag, och en defensiv funktion kan inte rädda dig om det omgivande systemet saknar redundans. Det här kapitlet bygger också på kapitel 2.9 (programvarukonstruktion), där felhantering var en disciplin bland många. Här blir den hela ämnet.

För stora team är enhetlighet priset. När hundratals ingenjörer hanterar fel hundratals olika sätt blir varje tjänst ett pussel och varje incident en utgrävning. I företagsmiljöer höjer den inkonsekvensen kostnaden för varje revision och varje integration. I myndigheter och andra högriskiga system är insatserna skarpare: korrekthet, säkert fel och ett tydligt revisionsspår är inte funktioner du lägger till senare utan egenskaper systemet måste ha från första commit. Ett bidragssystem som i tysthet räknar fel, eller ett registersystem som tappar ett fel utan att logga det, är inte bara buggigt. Det är otillförlitligt på ett sätt som urholkar institutionen bakom det.

Nyckelprinciper

  • Skilj fel, defekter och haverier åt, och hantera var och en på rätt lager.
  • Välj falla-snabbt eller falla-säkert medvetet, per sammanhang, aldrig av en slump.
  • Gör felhanteringskontraktet för varje funktion och API uttryckligt och ärligt.
  • Validera vid gränser. Lita inuti dem. Försvara utan paranoia.
  • Svälj aldrig ett fel tyst. Lyft det, omslut det eller hantera det med avsikt.
  • Gör omförsök säkra med idempotens, tidsgränser, fördröjning och jitter.
  • Ge felvägen samma designuppmärksamhet som den lyckade vägen.

Rekommendationer

Skilj fel, defekter och haverier åt

Slarvigt språk ger slarvig hantering, så börja med tydliga termer. En defekt (fault) är en brist i systemet: en bugg, en dålig konfiguration, ett beroende som är nere. Ett fel (error) är det felaktiga interna tillstånd som en defekt producerar: ett null där ett värde borde vara, ett saldo som inte längre stämmer av. Ett haveri (failure) är vad den yttre betraktaren ser: begäran returnerar fel svar, eller inget svar. En defekt kan orsaka många fel, och många fel kan fångas innan något blir ett synligt haveri. Hela poängen med felhantering är att bryta den kedjan, att fånga felet innan det blir ett haveri användaren eller revisorn upplever.

Det här ordförrådet talar också om var du ska agera. Defekter åtgärdas i granskning, testning och konfiguration. Fel åtgärdas vid körning av mönstren i det här kapitlet. Haverier åtgärdas av observerbarhet (kapitel 9.2) och av systemnivåmotståndskraften i kapitel 3.5. När ditt team delar dessa ord blir incidentgranskningar skarpare: du kan säga exakt var kedjan borde ha brutits och inte gjorde det, i stället för att gräla om vad “buggen” var.

Välj falla-snabbt eller falla-säkert per sammanhang

Falla snabbt betyder att stanna i samma stund något är fel och vägra fortsätta på dåligt tillstånd så att problemet visar sig högljutt och nära sin orsak. Falla säkert betyder att degradera till ett känt, ofarligt tillstånd och fortsätta betjäna det du säkert kan. Ingetdera är universellt rätt, och skickligheten är att välja per sammanhang. Under utveckling och vid interna gränser är falla snabbt din vän: ett program som stannar vid en bruten invariant ger dig ett kort stackspår i stället för ett långt mysterium. I produktion, vid kanterna av ett användarvänt system, vinner ofta falla säkert: en rekommendationspanel som returnerar ingenting är bättre än en kassasida som inte laddas.

Besluta detta medvetet för varje gräns och skriv ner beslutet. En styrsystem- eller medicinteknisk komponent faller säkert in i ett definierat tillstånd eftersom att fortsätta på korrupta data kunde skada någon. Ett huvudboksbokföring faller snabbt eftersom att bokföra en felaktig post är värre än att bokföra ingen. Fel parning är farlig i båda riktningar: falla säkert där du behövde falla snabbt döljer korruption, och falla snabbt där du behövde falla säkert förvandlar ett kosmetiskt hickande till ett avbrott.

Välj din felsignaleringsmekanism och använd den konsekvent

Språk ger dig två breda sätt att signalera att något gick fel. Undantagshantering kastar ett objekt uppför anropsstacken tills någon hanterare fångar det, och skiljer felvägen från huvudlogiken. Alternativet är uttryckliga felvärden: funktionen returnerar både ett resultat och ett fel, och anroparen måste inspektera båda. Många moderna språk formaliserar det senare med en resultattyp, ofta kallad Result eller Either, som tvingar anroparen att packa upp antingen en framgång eller ett misslyckande innan värdet används. Varje tillvägagångssätt har en kostnad. Undantag håller den lyckade vägen ren men kan dölja kontrollflöde och locka utvecklare till catch-all-block som raderar information. Uttryckliga resultat gör varje misslyckande synligt i typsignaturen men lägger till ceremoni och kan ignoreras om språket inte tvingar fram kontrollen.

Det rätta svaret handlar mindre om vilken mekanism än om enhetlighet och ärlighet. Välj det idiom ditt språk och ekosystem föredrar och tillämpa det enhetligt över dina tjänster så att en läsare alltid vet hur misslyckande färdas. Reservera undantag för genuint exceptionella villkor, inte vanligt kontrollflöde som “användare hittades inte”, som bättre modelleras som ett normalt resultat. Vad du än väljer, låt aldrig ett misslyckande bli osynligt: ett okontrollerat felvärde är lika farligt som ett tomt catch-block. I en stor kodbas slår en skriven konvention plus en linter som flaggar ignorerade fel varje individs preferens.

Gör felhanteringskontraktet uttryckligt

Varje funktion och varje API har ett felhanteringskontrakt, oavsett om någon skrivit ner det. Det besvarar: vad kan gå fel här, hur får du veta om det och vad är garanterat om tillståndet när det händer? Gör kontraktet uttryckligt. Dokumentera vilka fel en funktion kan returnera eller kasta, skilj återhämtningsbara fel (anroparen kan rimligen försöka om eller falla tillbaka) från icke återhämtningsbara (anroparen kan inte åtgärda detta och bör vidarebefordra eller avbryta) och ange om funktionen lämnar tillståndet oförändrat vid misslyckande. Den sista egenskapen, ibland kallad den starka undantagsgarantin, betyder att ett misslyckat anrop är som om det aldrig hänt, vilket är exakt det som låter en anropare försöka om säkert.

För ett publikt eller teamöverskridande API är det här kontraktet en del av gränssnittet, lika verkligt som parametertyperna. Utforma en liten, stabil feltaxonomi: en avgränsad uppsättning kategorier som valideringsfel, hittades inte, konflikt, obehörig, beroende-otillgängligt och internt fel. Anropare kan då grena på kategori utan att tolka strängar. En tydlig taxonomi gör felhantering komponerbar över många tjänster, och den gör misslyckanden granskningsbara, eftersom varje misslyckande kartläggs till en känd, namngiven sort.

Validera vid gränser och försvara utan paranoia

Behandla data som korsar en förtroendegräns (en nätverksbegäran, en fil, användarindata, ett meddelande från en annan tjänst) som fientliga tills de validerats, och validera dem vid gränsen, en gång, noggrant. Det är defensiv programmering tillämpad med omdöme. Inuti en modul vars indata du redan validerat döljer redundanta kontroller på varje rad logiken och undertrycker just de fel du skulle vilja se. Disciplinen är: försvara hårt vid kanterna, lita inuti dem. Validera struktur, intervall och invarianter där data kommer in, konvertera dem till typer som gör otillåtna tillstånd orepresenterbara och låt den inre koden anta att den arbetar med rena data.

Paranoia har en verklig kostnad. Kod kvävd i nollkontroller och defensiva grenar är svårare att läsa, och värre, den förvandlar ofta ett tydligt misslyckande till en tyst axelryckning och returnerar ett standardvärde där den borde ha utlöst ett larm. Defensivitet som maskerar buggar är inte säkerhet. Det är uppskjutande.

Gör omförsök säkra, begränsade och artiga

Många defekter är övergående: ett ögonblickligt nätverksglapp, en tjänst som startar om, kortvarig låskonkurrens. I varje distribuerat system (kapitel 3.3) är dessa partiella fel normalfallet snarare än undantaget. Att försöka om är det naturliga svaret, men en naiv omförsöksloop är ett laddat gevär. Gör först operationen du försöker om idempotent, vilket betyder att utföra den två gånger har samma effekt som att utföra den en gång. Utan idempotens kan ett omförsök efter en tidsgräns debitera ett kort två gånger eller skapa två poster, eftersom du inte kan avgöra om det första försöket misslyckades eller bara dess kvittens gick förlorad. Använd idempotensnycklar för skrivningar så att mottagaren kan känna igen och deduplicera en upprepning.

Sätt för det andra en tidsgräns på varje fjärranrop så att ett hängt beroende inte kan hänga dig. Sprid för det tredje omförsök med exponentiell fördröjning, fördubbla väntan efter varje försök, och lägg till jitter (en liten slumpmässig fördröjning) så att tusen klienter som återhämtar sig samtidigt inte synkroniserar sig till en stampede som slår ned den återhämtande tjänsten igen. Sätt för det fjärde tak på antalet omförsök och den totala tiden och ge sedan upp graciöst. Omförsök utan gränser, fördröjning, jitter och idempotens är ett av de vanligaste sätten ett litet hickande blir ett självförvållat avbrott.

Lägg till kretsbrytare, skott och graciös nedgradering i koden

När ett beroende verkligen är nere slösar omförsök bara kraft och fördjupar hålet. En kretsbrytare bevakar felfrekvensen för anrop till ett beroende och “öppnar”, när fel passerar en tröskel, för att falla omedelbart under en avkylningsperiod i stället för att vänta på dödsdömda anrop. Efter avkylningen släpper den igenom ett provanrop och stänger igen om beroendet har återhämtat sig. Det skyddar både dina anropare (snabba, förutsägbara fel i stället för travade tidsgränser) och det kämpande beroendet (andrum att återhämta sig). Skottmönstret, uppkallat efter ett skepps vattentäta avdelningar, isolerar resurser så att ett mättat beroende inte kan förbruka varje tråd eller anslutning och sänka hela processen. Du ger varje beroende sin egen begränsade pool.

Dessa mönster paras med graciös nedgradering på kodnivå: när ett icke väsentligt beroende är otillgängligt, returnera ett reducerat men användbart resultat i stället för ett fel. Visa cachade data med en notering om inaktualitet, dölj personaliseringspanelen, köa skrivningen till senare. Det är det lokala komplementet till systemnivåmotståndskraften i kapitel 3.5: arkitekturen ger redundans över maskiner, och din kod ger vettigt beteende när en del saknas.

Omslut fel med sammanhang och svälj dem aldrig

Ett fel som lyder “anslutning vägrad” tio lager upp från där det hände är nästan värdelöst. När ett fel propagerar, omslut det med sammanhang: vad du försökte göra, vilken entitet eller begäran, vilket beroende, medan du bevarar den ursprungliga orsaken så att roten inte går förlorad. Bra språk och bibliotek stöder denna felkedjning direkt. Målet är att en enda loggrad talar om för jourhavande ingenjör vad som misslyckades, under vilken operation, för vilken indata. Det är råmaterialet för observerbarheten i kapitel 9.2 och felsökningen i kapitel 2.15.

Den kardinala synden är att svälja ett fel: ett tomt catch-block, ett ignorerat returvärde, ett catch som loggar på debugnivå och fortsätter som om ingenting hänt. Ett svalt fel försvinner inte. Det dyker upp igen senare som korrumperade data eller en oförklarlig defekt, nu fristående från sin orsak. Varje fel måste möta ett av tre öden: hantera det (återhämta eller degradera), omsluta och vidarebefordra det eller, högst upp i stacken, logga det med fullt sammanhang och falla. Om du fångar ett fel och inte gör någon av dessa har du valt att dölja en framtida incident för ditt framtida jag.

Avvägningar: för- och nackdelar

TillvägagångssättFördelarNackdelar
UndantagRen lyckad väg. Svåra att ignorera om okontrolleradeDolt kontrollflöde. Lockar till catch-all-radering
Uttryckliga felvärden / resultattyperMisslyckande synligt i signaturen. Tvingar hanteringMer ceremoni. Kan ignoreras utan upprätthållande
Falla snabbtBlottlägger buggar högljutt, nära orsakenDålig användarupplevelse om det används vid kanten
Falla säkertFortsätter betjäna. Skyddar användare och dataKan maskera korruption om det används där du behövde falla snabbt
Omförsök med fördröjningRider ut övergående defekter automatisktFörstärker belastning och dubbelskrivningar utan idempotens
KretsbrytareSnabba fel. Låter beroenden återhämta sigTillagt tillstånd och justering. Kan maskera ett ihållande problem
Defensiv validering vid gränserFångar dåliga data tidigt, en gång, högljuttÖverdriven belamrar den logiken och döljer verkliga fel

Den centrala spänningen är mellan synlighet och brus. Hantera fel för tyst och du döljer problem tills de är dyra. Hantera dem för högljutt och överallt och du dränker signalen i ceremoni och maskerar de fel som spelar roll. Lös det genom plats och avsikt. Var högljudd och strikt vid gränser, där dåliga data och beroendefel kommer in. Var tyst och förtroendefull i det inre, där indata redan är rena. Besluta falla snabbt mot falla säkert per gräns och skriv ner det. Målet är kod där varje fel har exakt en tydlig ägare och ett tydligt öde, och inget faller tyst mellan stolarna.

Frågor att diskutera med ditt team

  1. Har vi en gemensam feltaxonomi och felhanteringskonvention över våra tjänster, eller improviserar varje team? I ett stort team är det här skillnaden mellan fel som komponeras och fel som förvirrar. När en tjänst returnerar HTTP 500 för ett valideringsproblem, en annan kastar ett typat undantag och en tredje returnerar ett null blir varje integration en förhandling och varje incident en översättningsövning. Ta med exempel på samma logiska fel, säg “posten hittades inte”, så som det visar sig över tre av era tjänster, och se hur olika de signalerar det. Svaret bör bli en skriven standard: en avgränsad uppsättning felkategorier, ett konsekvent sätt att signalera dem och en linter eller granskningschecklista som upprätthåller det. Enhetlighet här betalar sig i varje framtida integration, revision och jourpass.

  2. Har vi för varje kritisk gräns valt falla snabbt eller falla säkert med avsikt, och matchar koden det valet? De flesta team har aldrig fattat detta beslut uttryckligen, vilket betyder att det fattades åt dem av den som skrev koden först, och inkonsekvent. De motstridiga hänsynen är verkliga: att falla säkert håller användare betjänade men kan låta korruption spridas, medan att falla snabbt skyddar data men kan förvandla ett mindre beroendeavbrott till ett synligt misslyckande. Ta med er incidenthistorik och fråga, för de värsta, om koden föll som ni skulle ha valt om ni frågats i förväg. Beläggen ni vill ha är en karta över era gränser med en medveten etikett på varje, särskilt överallt där pengar, säkerhet eller medborgarregister är inblandade. Där etiketten och koden är oense har ni hittat er nästa rättelse.

  3. När övade vi senast en felväg med avsikt, och betedde den sig som designat? Felvägen är vanligen den minst testade kod ni äger, men det är där förtroende vinns eller förloras, och “vi faller säkert” är ett påstående ni inte kan styrka om ni aldrig sett det hända. En omförsöksloop utan idempotens, en kretsbrytare vars tröskel är fel, ett svalt undantag i en sällan träffad gren: dessa gömmer sig tills en verklig incident hittar dem åt er. Ta med resultaten av att avsiktligt injicera fel (ett dödat beroende, en framkallad tidsgräns, en felformad nyttolast) i en realistisk miljö. Åtgärden som följer är att göra felinjektion rutinmässig, så att återhämtning, nedgradering och säkert felbeteende verifieras kontinuerligt snarare än hoppas på. Varje felväg ni aldrig har utlöst är ett löfte ni inte har testat.

  4. Vilka av våra skrivoperationer är idempotenta, och var skulle ett omförsök efter en förlorad kvittens duplicera en verklig effekt som en betalning eller en post? Att försöka om är den vanligaste motståndskraftsreflexen och, gjort vårdslöst, det vanligaste sättet ett övergående glapp blir duplicerade pengar eller data. I ett stort team bor omförsökslogik ofta i delade klienter, mellanprogram och enskilda tjänster på en gång, så en enda skrivning kan försökas om på flera lager utan att någon äger det totala beteendet. Det motstridiga draget är att idempotensnycklar, deduplicering och lagrade begäranutfall lägger till lagring och kod, och team under leveranstryck hoppar över dem för skrivningar de felaktigt antar är säkra. Ta med en inventering av era externt synliga skrivningar, var och en markerad för om den bär en idempotensnyckel och hur mottagaren känner igen och deduplicerar en upprepning. I företags- och myndighetsmiljöer, flagga de som flyttar pengar eller ändrar en medborgares post först, eftersom en dubbel betalning eller ett duplicerat bidrag är ett revisionsfynd och ibland en rättslig exponering, inte bara en defekt.

  5. Kommer våra tidsgränser, kretsbrytare och skott från ett gemensamt, testat bibliotek, eller handrullar varje team dem? Dessa mönster är lätta att beskriva och lätta att få subtilt fel: en saknad tidsgräns, en brytartröskel som aldrig löser ut, en anslutningspool dimensionerad så att ett långsamt beroende svälter hela processen. När varje team implementerar dem på nytt ackumulerar ni många något trasiga kopior och ingen enskild plats att åtgärda en brist en gång när ni hittat den. Den motstridiga hänsynen är att ett gemensamt bibliotek påtvingar ett gemensamt gränssnitt och en gemensam uppgraderingstakt, och team med ovanliga körtider eller latensbehov kan sträva emot eller gå runt det. Ta med en kartläggning av hur många skilda omförsöks- och brytarimplementationer som faktiskt körs i produktion, och vilka tjänster som fortfarande inte har någon tidsgräns alls på sina utgående anrop. För ett stort företag eller en myndighet ger ett granskat gemensamt bibliotek också säkerhetsgranskare och revisorer en komponent att certifiera snarare än dussintals, vilket sänker kostnaden för varje granskning.

  6. Om en incident hände i natt, kunde vilken ingenjör med jour som helst spåra den från en enda loggrad, och kunde en revisor senare se varje fel systemet registrerade? Ett omslutet, kategoriserat, väl loggat fel är skillnaden mellan en diagnos på tio minuter och en utgrävning vid midnatt, och ett svalt är en framtida incident ni har dolt för er själva. I ett stort team korsar fel många tjänstehopp, så värdet kommer från konsekvent sammanhang och korrelationsidentifierare som överlever de hoppen, inte från något enskilt teams flit. Den motstridiga spänningen är kostnad och brus: logga allt och ni dränker signalen och betalar för att lagra den. Logga för lite och ni kan inte rekonstruera vad som hände. Ta med ett verkligt nyligt fel och gå dess spår från början till slut och notera varje hopp där sammanhang tappades eller ett fel fångades och kasserades. I reglerade och offentliga system, behandla detta som en regelefterlevnadsegenskap, eftersom ett ogranskningsbart fel, eller ett beslut ni inte kan förklara år senare, är en rättslig exponering och inte bara en operativ lucka.

Sektorsperspektiv

Startup. Med en handfull ingenjörer och ingen löptid att undvara, spendera din felhanteringsbudget där ett fel kostar dig en kund eller dina data: sätt en tidsgräns på varje utgående anrop, gör dina penningflyttande skrivningar idempotenta och lägg till en lintregel mot ignorerade fel. Hoppa över det utarbetade ramverket. En resultattyp för kärnfunktioner och graciös nedgradering på icke kritiska beroenden köper det mesta av säkerheten för några dagars arbete. Fall snabbt under utveckling så att buggar visar sig högljutt och stå emot att handrulla en kretsbrytare innan du faktiskt har ett beroende som motiverar en.

Småföretag. Utan motståndskraftsspecialist i personalen och med snäv budget, lita på det ditt språk, ramverk och din molnleverantör redan ger snarare än att bygga mönster från grunden: hanterade köer, leverantörssidiga omförsök och bibliotekstidsgränser täcker mer än de flesta team väntar sig. Rama in beslutet som köp mot bygg och köp varhelst ett moget beroende hanterar omförsök, fördröjning och idempotens åt dig. Lägg din knappa uppmärksamhet på den eller de två gränser där en felaktig eller förlorad transaktion verkligen skulle skada, och se till att de faller säkert och lämnar ett spår.

Storföretag. Över många team är priset enhetlighet: en gemensam feltaxonomi, ett gemensamt bibliotek för tidsgränser, omförsök, kretsbrytare och skott och en linter och granskningschecklista som upprätthåller dem i pipelinen. Mata varje fel till en enhetlig observerbarhetsplattform med korrelationsidentifierare så att ett fel kan spåras över tjänstehopp, och standardisera beslut om falla snabbt mot falla säkert per gräns så att revisioner finner ett dokumenterat, försvarbart mönster i stället för en spridning av lokala vanor. Styr det gemensamma biblioteket som en verklig produkt, för en brist åtgärdad där är en brist åtgärdad överallt.

Offentlig sektor. Korrekthet, säkert fel och ett varaktigt revisionsspår är skyldigheter, inte preferenser. Fall snabbt vid varje bruten invariant som rör pengar eller behörighet, validera varje medborgarvänt indata vid gränsen och skriv varje fel till en oföränderlig logg med tillräckligt sammanhang för att ett beslut ska kunna förklaras och granskas år senare. Upphandling och långa systemlivslängder betyder att felkontrakten måste dokumenteras så att tjänstemän kan underhålla koden långt efter att de ursprungliga författarna gått, och varje leverantörskomponent måste exponera sitt felbeteende snarare än dölja det bakom ett ogenomskinligt gränssnitt.

Exempel

Startup. En startup med fyra personer levererar en app som anropar en tredjepartsbetalningsleverantör och en e-posttjänst. Tidigt lägger de till en naiv omförsöksloop och dubbeldebiterar omedelbart en kund när en tidsgräns maskerar en lyckad debitering. Rättelsen lär lektionen: de lägger till idempotensnycklar på varje skrivning, sätter en tidsgräns på varje utgående anrop och byter till exponentiell fördröjning med jitter. De antar en resultattyp för kärntjänstfunktioner så att fel syns i signaturen, och en lintregel flaggar varje ignorerat fel. När e-postutskick misslyckas degraderar kassan graciöst genom att köa meddelandet i stället för att blockera försäljningen. Disciplinen kostar några dagar och besparar dem en klass av incidenter som skulle ha kostat långt mer i återbetalningar och förtroende.

Storföretag. Ett globalt logistikföretag kör hundratals tjänster och standardiserar felhantering över alla. Varje tjänst kartlägger fel till en gemensam taxonomi (validering, hittades inte, konflikt, beroende-otillgängligt, internt), så att anropare grenar på kategori snarare än att tolka meddelanden. Ett gemensamt bibliotek tillhandahåller kretsbrytare, begränsade omförsök med fördröjning och jitter och anslutningspooler med skott, så att ingen handrullar dessa mönster fel. Varje fel loggas med korrelationssammanhang som matar observerbarhetsplattformen i kapitel 9.2, så att en ingenjör med jour kan spåra ett fel över tjänstehopp från en enda rad. Eftersom standarden är enhetlig och upprätthålls i pipelinen rör sig ingenjörer tryggt över obekanta tjänster och revisorer kan se att varje fel registreras, kategoriseras och är spårbart.

Offentlig sektor. En nationell bidragsmyndighet bygger ett behörighets- och betalningssystem där ett felaktigt svar kan neka någon hyra eller överbetala ur den offentliga kassan. Korrekthet och säkert fel är icke förhandlingsbara, så koden faller snabbt vid varje bruten finansiell invariant: en beräkning som inte kan stämmas av vägrar bokföra i stället för att bokföra en felaktig siffra. Varje medborgarvänt indata valideras vid gränsen, och otillåtna tillstånd görs orepresenterbara i domäntyperna. Varje fel skrivs till en oföränderlig revisionslogg med fullt sammanhang, vilket uppfyller det rättsliga kravet att beslut ska kunna förklaras och granskas år senare. Där ett icke kritiskt beroende som dokumentförhandsvisning är nere degraderar systemet graciöst så att en handläggare ändå kan bearbeta ärendet. Nya tjänstemän ärver kod vars felkontrakt är dokumenterade, så att de kan underhålla den säkert långt efter att de ursprungliga författarna gått vidare.

Affärsnytta: motiv, ROI och TCO

Avkastningen på disciplinerad felhantering syns som färre incidenter, kortare incidenter och billigare incidenter. De flesta produktionsavbrott är inte exotiska. De spåras till ett svalt undantag, en saknad tidsgräns, en omförsöksstorm eller en gräns som litade på data den borde ha validerat. Var och en går att förebygga med mönstren här, och varje förhindrad incident sparar inte bara den direkta kostnaden för driftstopp utan de växande kostnaderna för nödåtgärder, kundavhopp och utredning. Eftersom ett omslutet, väl loggat fel kan diagnostiseras på minuter snarare än timmar sjunker genomsnittlig återställningstid, och andelen misslyckade ändringar sjunker med den när ingenjörer slutar frukta felvägen.

Kostnaden för att införa är måttlig och mest engångs. Du skriver ner en feltaxonomi, tillhandahåller ett gemensamt bibliotek för omförsök och kretsbrytare så att team inte uppfinner dem dåligt, lägger till lintregler mot ignorerade fel och bygger vanan med felinjektion. Kostnaden för försummelse växer tyst: svalda fel samlas till korrumperade data som är dyra att reda ut, och inkonsekvent hantering multiplicerar kostnaden för varje integration och varje revision. I reglerade och offentliga miljöer är ett ogranskningsbart fel en regelefterlevnads- och rättslig exponering, inte bara ett tekniskt problem. För att argumentera inför ledningen, koppla felhanteringsdisciplin till de mått de redan bevakar: incidentfrekvens, genomsnittlig återställningstid, andel misslyckade ändringar och revisionsfynd.

Antimönster och fallgropar

  • Tyst sväljning: tomma catch-block och ignorerade returvärden som förvandlar ett misslyckande till ett fördröjt, fristående mysterium.
  • Catch-all-radering: ett brett catch som loggar ett generiskt meddelande och kasserar det ursprungliga felet och dess sammanhang.
  • Omförsök utan idempotens: att köra om icke-idempotenta skrivningar efter en tidsgräns, dubbeldebitera eller duplicera poster.
  • Omförsöksstormar: ingen fördröjning, inget jitter och inget tak, så klienter synkroniserar och hamrar ett återhämtande beroende ned igen.
  • Inga tidsgränser: obegränsade fjärranrop som låter ett hängt beroende utmatta trådar och frysa hela processen.
  • Undantag som kontrollflöde: att kasta och fånga för vanliga utfall som “hittades inte”, vilket döljer logik och gör koden långsam.
  • Defensiv paranoia: kontroller på varje rad som begraver logiken och förvandlar verkliga fel till tysta standardvärden.
  • Strängtypade fel: anropare som tolkar felmeddelandetext eftersom det saknas en stabil, kategoriserad taxonomi att grena på.
  • Falla säkert där du behövde falla snabbt: att fortsätta på korrupt tillstånd i ett system där ett felaktigt svar är värre än inget.

Mognadsmodell

  • Nivå 1, Initiera: Felhantering är ad hoc och reaktiv, beslutad per utvecklare. Tomma catch-block och ignorerade returvärden är vanliga, omförsök är naiva, tidsgränser saknas och fel visar sig som korrumperade data eller mystiska defekter utan konsekvent loggning.
  • Nivå 2, Utveckla: Team antar grundläggande praxis, men inkonsekvent. Fel loggas med visst sammanhang, uppenbar sväljning avråds från i granskning och tidsgränser och enkla omförsök finns, men konventioner varierar mellan tjänster, idempotens är fläckig och felvägen testas sällan.
  • Nivå 3, Standardisera: En gemensam feltaxonomi och hanteringskonvention är dokumenterade och upprätthålls i hela organisationen. Gränsvalidering, idempotenta omförsök med fördröjning och jitter, kretsbrytare, skott och felomslutning är standard, tillhandahållna av gemensamma bibliotek, och varje fel matar en enhetlig observerbarhetspipeline.
  • Nivå 4, Hantera: Felhanteringsbeteende mäts mot utgångslägen och styrs med data. Omförsöksfrekvenser, kretsbrytarutlösningar, antal tidsgränser, fynd av svalda fel från statisk analys, genomsnittlig återställningstid och andel misslyckade ändringar följs per tjänst. Kretsbrytartrösklar och tidsgränser justeras utifrån observerad latens och felmönster snarare än gissas. Felinjektion körs enligt schema. Och team granskar dessa mått för att fånga regressioner och hålla varje falla-snabbt- eller falla-säkert-val mot belägg.
  • Nivå 5, Orkestrera: Motståndskraft är integrerad med leverans- och riskplanering och förbättras kontinuerligt. Taxonomin, de gemensamma biblioteken och standarderna utvecklas utifrån varje incident, kaos- och felinjektionsexperiment är rutin och organisationen anpassar tidsgränser, brytartrösklar, nedgraderingsstrategier och gränsbeslut när trafik, beroenden och riskbilden förskjuts.

Idéer för diskussion

  1. Var i er kodbas sväljs ett fel just nu, och hur skulle ni veta om ni har fel i att det inte gör det?
  2. Vilka av era skrivoperationer är idempotenta, och vilka skulle köras dubbelt om ett omförsök utlöstes efter en förlorad kvittens?
  3. Bör “användare hittades inte” vara ett undantag, ett felvärde eller ett normalt resultat, och svarar ert team konsekvent på det?
  4. Vad är er faktiska regel för var validering sker, och kan ni peka på en gräns som litar på data den inte borde?
  5. Hur avgör ni tröskeln och avkylningen för en kretsbrytare, och hur skulle ni veta att de nuvarande inställningarna är fel?
  6. Om en revisor bad att få se varje fel ert system upplevde förra månaden, kunde ni producera det, kategoriserat och med sammanhang?

Viktigaste punkter

  • Skilj defekter, fel och haverier åt, och bryt kedjan innan ett internt fel blir ett synligt haveri.
  • Välj falla snabbt eller falla säkert medvetet per gräns och gör felhanteringskontraktet för varje funktion uttryckligt.
  • Validera hårt vid förtroendegränser och lita inuti dem. Defensivitet som maskerar fel är uppskjutande, inte säkerhet.
  • Gör omförsök säkra med idempotens, tidsgränser, exponentiell fördröjning och jitter, och lägg till kretsbrytare och graciös nedgradering i koden.
  • Omslut fel med sammanhang, mata dem till observerbarhet och svälj dem aldrig. Varje fel måste hanteras, vidarebefordras eller loggas och lyftas.

Referenser och vidare läsning

  • Michael T. Nygard, Release It! Design and Deploy Production-Ready Software
  • Andrew Hunt and David Thomas, The Pragmatic Programmer
  • Steve McConnell, Code Complete: A Practical Handbook of Software Construction
  • Betsy Beyer, Chris Jones, Jennifer Petoff, and Niall Richard Murphy (eds.), Site Reliability Engineering: How Google Runs Production Systems
  • Marc Brooker, “Timeouts, Retries, and Backoff with Jitter,” Amazon Builders’ Library
  • Martin Fowler, “CircuitBreaker,” martinfowler.com
  • Nassim Nicholas Taleb, Antifragile: Things That Gain from Disorder