Hur man hanterar undantag i Elixir

Undantagshantering är en bra övning för alla mjukvaruutvecklingsmetoder. Oavsett om det gäller testbaserad utveckling, smidiga sprints eller en hackingsession med bara en bra gammal todo-lista, kan vi alla dra nytta av att våra baser är täckta med ett robust tillvägagångssätt vid felhantering. 

Det är av största vikt att se till att fel tas hand om, samtidigt som det är estetiskt tilltalande och naturligtvis inte blir ett stort problem logiskt med kryptiska meddelanden för slutanvändaren att försöka få bort betydelsen från. Om du gör det är du verkligen på en bra väg att skapa en stabil, stabil och klibbig app som användarna trivs med och kommer att rekommendera starkt till andra.

Idealiskt för oss erbjuder Elixir omfattande undantagshantering via flera mekanismer som försök fånga, kastar, och den : fel, orsak tupel.

För att visa ett fel, använd höja i ditt interaktiva skal för att få en första smak:

iex> höja "Oh någotz!" ** (RuntimeError) Oh någotz!

Vi kan också lägga till en typ till så här:

iex> höj ArgumentError, meddelande: "error message here ..." ** (ArgumentError) felmeddelande här ... 

Hur felhantering fungerar i Elixir

Några av de sätt som fel behandlas i Elixir kan inte vara uppenbara vid första anblicken.

  • För det första om processer-i användning rom, Vi kan skapa självständiga processer. Det betyder att ett misslyckande på en tråd inte skulle påverka någon annan process, om det inte fanns någon koppling på något sätt. Men som standard kommer allt att vara stabilt.
  • För att meddela systemet om ett fel i någon av dessa processer kan vi använda spawn_link makro. Detta är en dubbelriktad länk, vilket innebär att om en länkad process upphör, utlöses en utgångssignal.
  • Om utgångssignalen är något annat än :vanligt, vi vet att vi har ett problem. Och om vi fäller utgångssignalen med Process.flag (: trap_exit, true), Utgångssignalen skickas till processens brevlåda, där logiken kan placeras på hur man hanterar meddelandet och därigenom undviker en hård krasch.
  • Slutligen har vi bildskärmar, som liknar spawn_links, men dessa är enriktade länkar, och vi kan skapa dem med Process.monitor
  • Processen som påstår Process.monitor kommer att få felmeddelanden vid fel.

För ett provfel, försök att lägga till ett tal till en atom och du kommer att få följande:

iex>: foo + 69 ** (ArithmeticError) dåligt argument i aritmetiskt uttryck: erlang. + (: foo, 69)

För att säkerställa att slutanvändaren inte blir felaktig kan vi använda försöket, fångst och räddningsmetoder som tillhandahålls av Elixir.

Försök / Rescue

Först i vår verktygslåda för undantagshantering är prova / räddning, som fångar fel som produceras genom att använda höja så är det bäst lämpat för utvecklarfel eller exceptionella omständigheter som inmatningsfel.

prova / räddning är liknande för användning till a försök fånga block du kanske har sett på andra programmeringsspråk. Låt oss titta på ett exempel i åtgärd:

iex> försök ...> höja "misslyckas!" ...> rädda ...> e i RuntimeError -> IO.puts ("Error:" <> e.message) ...> slutfel: misslyckas! :ok

Här använder vi prova / räddning blockera och ovannämnda höja att fånga RuntimeError

Detta betyder ** (RuntimeError) standardutgång av höja visas inte och ersätts med en snyggare formaterad utgång från IO.puts ring upp. 

Som en bra metod måste du använda felmeddelandet för att ge användaren användbar produktutskrift på vanlig engelska, vilket hjälper dem med problemet. Vi tittar på det mer i nästa exempel.

Flera fel i en försök / räddning

En stor fördel med Elixir är att du kan fånga flera resultat i en av dessa prova / räddning block. Titta på detta exempel:

försök gör opts |> Keyword.fetch! (: source_file) |> File.read! räddning e i KeyError -> IO.puts "saknas: source_file option" e i File.Error -> IO.puts "kan inte läsa källfilens slut

Här har vi fångat två fel i rädda

  1. Om filen inte kan läsas.
  2. Om :källfilen symbolen saknas. 

Som tidigare nämnts kan vi använda det här för att göra felmeddelanden som är lätt att förstå för slutanvändaren. 

Denna kraftfulla och minimala syntaxanvändning av Elixir gör att du skriver flera kontroller som är mycket tillgängliga för oss för att kontrollera många möjliga misslyckanden, på ett snyggt och koncist sätt. Detta hjälper oss att se till att vi inte behöver skriva utarbetade villkor som gör långvariga skript som kan vara svåra att visualisera fullständigt och felsöka korrekt under senare utveckling eller för att en ny utvecklare ska gå med.

Som alltid när man arbetar i Elixir är KISS det bästa sättet att ta. 

Efter

Det finns situationer när du behöver en särskild åtgärd som utförs efter försök / räddningsblocket, oavsett om det fanns något fel. För Java eller PHP-utvecklare kanske du tänker på prova / fångst / slutligen eller Ruby s börja / räddning / säkerställa.

Låt oss ta en titt på ett enkelt exempel på att använda efter.

iex> försök ...> höja "Jag vill prata med chefen!" ...> rädda ...> e i RuntimeError -> IO.puts ("Ett fel inträffade:" <> e.message) ...> efter ...> IO.puts "Oavsett vad som händer uppstår jag alltid som en dålig öre." ...> slutet Ett fel inträffade: Jag vill prata med chefen! Oavsett vad som händer uppstår jag alltid som en dålig öre. :ok

Här ser du efter används för att ständigt göra en meddelandeskärm (eller det kan vara en funktion som du vill kasta in där).

En mer vanlig praxis hittar du här används på är var filen öppnas, till exempel här:

: ok, file = File.open "would_defo_root.jpg" försök göra # Prova åtkomst till filen här efter # Se till att vi städa upp efteråt File.close (file) end

Utlöser

Så väl som höja och försök fånga metoder vi har skisserat tidigare, vi har också kasta och fånga makron.

Använda kasta Metoden avslutar utförandet med ett specifikt värde som vi kan leta efter i vårt fånga blockera och använd vidare som så:

iex> försök göra ...> för x <- 0… 10 do… > om x == 3 gör: kasta (x) ...> IO.puts (x) ...> slutet ...> fånga ...> x -> "Fångad: # x" ...> slut 0 1 2 "Fångad: 3"

Så här har vi förmågan att fånga något vi kasta inuti försöksblocket. I det här fallet är den villkorliga om x == 3 är utlösaren för vår göra: kasta (x).

Utgången från iterationen som produceras från for loop ger oss en klar förståelse av vad som har hänt programmatiskt. Inkrementellt har vi gått framåt och utförandet har blivit stoppat på fånga.  

På grund av denna funktionalitet kan det ibland vara svårt att visa var kasta fånga skulle implementeras i din app. En viktig plats skulle vara att använda ett bibliotek där API: n inte har tillräcklig funktionalitet för alla resultat som presenteras för användaren, och en fångst skulle räcka för att snabbt navigera kring problemet, snarare än att behöva utveckla mycket mer inom biblioteket för att hantera Frågan och returnera på lämpligt sätt för den.

Avslutar

Slutligen har vi i vår Elixir felhanteringsarsenal den utgång. Avslutande sker inte genom presentbutiken, men uttryckligen när en process dör. 

Utgångar signaleras så här:

iex> spawn_link fn -> exit ("du är klar son!") slut ** (EXIT från #PID<0.101.0>) "du är klar son!"

Utgångssignaler utlöses av processer av en av följande tre skäl:

  1. En vanlig utgång: Detta händer när en process har slutfört sitt jobb och avslutar körningen. Eftersom dessa utgångar är helt normala, behöver vanligtvis ingenting göras när de händer, ungefär som a exit (0) i C. Utgångsskälen för denna typ av utgång är atomen :vanligt.
  2. På grund av obehandlade fel: Detta händer när ett uncaught undantag höjs inuti processen, med nej prova / fångst / räddning blockera eller kasta / fånga att hantera det.
  3. Kraftfullt dödad: Detta händer när en annan process skickar en utgångssignal med anledningen :döda, vilket tvingar mottagningsprocessen att avslutas.

Stapelspår

Vid en viss uppdateringstidpunkt på kasta, utgång eller fel, ringer till System.stacktrace kommer att returnera den senaste händelsen i den aktuella processen. 

Stackspåret kan formateras en hel del, men detta kan komma att ändras i nyare versioner av Elixir. För mer information om detta, se den manuella sidan.

För att returnera stapelspåret för den aktuella processen kan du använda följande:

Process.info (self (),: current_stacktrace)

Gör dina egna fel

Ja, Elixir kan också göra det. Naturligtvis har du alltid de inbyggda typerna som RuntimeError till ditt förfogande. Men skulle det inte vara trevligt om du kunde gå ett steg längre?

Att skapa din egen anpassade feltyp är lätt med hjälp av defexception makro, som bekvämt kommer att acceptera :meddelande alternativ, för att ställa in ett standardfelmeddelande som så:

defmodule MyError gör defexception meddelande: "ditt anpassade fel har inträffat" slutet

Så här använder du den i din kod:

iex> försök ...> upprepa MyError ...> rädda ...> e i MyError -> e ...> slut% MyError message: "din anpassade fel har uppstått"

Slutsats

Felhantering i ett metaprogrammeringsspråk som Elixir har en hel massa potentiella konsekvenser för hur vi utformar våra applikationer och gör dem robusta nog för en strikt basning av produktionsmiljön. 

Vi kan se till att slutanvändaren alltid lämnas med en ledtråd - ett enkelt och lättförståeligt styrmeddelande som inte kommer att göra deras uppgift svår, utan snarare den inversa. Felmeddelanden måste alltidskrivs på vanlig engelska och ge mycket information. Kryptiska felkoder och variabla namn är inte bra för de genomsnittliga användarna, och kan även förvirra utvecklare!

Vidare kan du övervaka de undantag som höjts i din Elixir-applikation och konfigurera specifik loggning för vissa problemfel, så att du kan analysera och planera din åtgärd, eller du kan titta på att använda en lösning utan hylla.

Tjänster från tredje part

Förbättra noggrannheten i vårt felsökningsarbete och möjliggöra övervakning av din apps stabilitet med dessa tredjepartstjänster tillgängliga för Elixir:

  • AppSignal kan vara mycket fördelaktigt för kvalitetssäkringsfasen i utvecklingscykeln. 
  • GitHub repo bugsnex är ett utmärkt projekt för att använda API-gränssnittet med Bugsnag för att ytterligare upptäcka defekter i din Elixir app.
  • Övervaka upptid, systemminne och fel med Honeybadger, vilket ger övervakning av produktionsfel så att du inte behöver babysit din app.

Utökar felhantering vidare

Vidare kan du önska att ytterligare utöka felsökningsfunktionerna för din app och göra din kod lättare att läsa. För detta rekommenderar jag att du kolla in det här projektet för elegant felhantering på GitHub.