Hoe uitzonderingen in Elixir te behandelen

Uitzonderingsafhandeling is een goede oefening voor elke software-ontwikkelingsmethode. Of het nu gaat om testgebaseerde ontwikkeling, behendige sprints of een hacking-sessie met slechts een goeie ouwe lijst, we kunnen er allemaal baat bij hebben om te zorgen dat onze bases worden gedekt door een robuuste aanpak van defectafhandeling.. 

Het is van het grootste belang om ervoor te zorgen dat fouten worden afgehandeld, terwijl het esthetisch verantwoord is en natuurlijk niet logisch een groot probleem wordt met cryptische berichten voor de eindgebruiker om te proberen de betekenis te achterhalen. Als je dat doet, ben je zeker op een geweldige manier om een ​​solide, stabiele en kleverige app te maken waar gebruikers graag mee werken en die het ten zeerste aanbevelen aan anderen.

Idealiter biedt Elixir uitgebreide uitzonderingsafhandeling via verschillende mechanismen zoals proberen te vangen, worpen, en de : error, reason tuple.

Gebruik om een ​​fout weer te geven verhogen in je interactieve shell om een ​​eerste voorproefje te krijgen:

iex> raise "Oh noez!" ** (RuntimeError) Oh neeez!

We kunnen hier ook een type aan toevoegen, zoals:

iex> raise ArgumentError, message: "error message here ..." ** (ArgumentError) foutmelding hier ... 

Hoe foutafhandeling werkt in Elixir

Sommige manieren waarop fouten in Elixir worden behandeld, zijn op het eerste gezicht misschien niet vanzelfsprekend.

  • Allereerst over processen - in gebruik paaien, we kunnen onafhankelijke processen creëren. Dat betekent dat een fout in een thread geen ander proces mag beïnvloeden, tenzij er op een of andere manier sprake is van een koppeling. Maar standaard blijft alles stabiel.
  • Om het systeem op de hoogte te stellen van een storing in een van deze processen, kunnen we de spawn_link macro. Dit is een bidirectionele link, wat betekent dat als een gekoppeld proces wordt beëindigd, een uitgangssignaal wordt geactiveerd.
  • Als het uitgangssignaal iets anders is dan : normaal, we weten dat we een probleem hebben. En als we het uitgangssignaal vangen met Process.flag (: trap_exit, true), het uitgangssignaal wordt naar de mailbox van het proces gestuurd, waar de logica kan worden geplaatst om het bericht af te handelen, waardoor een harde crash wordt vermeden.
  • Eindelijk hebben we monitoren, die vergelijkbaar zijn met spawn_links, maar dit zijn unidirectionele links, en we kunnen ze creëren Process.monitor
  • Het proces dat de Process.monitor ontvangt de foutmeldingen bij een storing.

Probeer voor een voorbeeldfout een getal toe te voegen aan een atoom en u krijgt het volgende:

iex>: foo + 69 ** (ArithmeticError) slecht argument in rekenkundige uitdrukking: erlang. + (: foo, 69)

Om ervoor te zorgen dat de eindgebruiker niet wordt vervormd, kunnen we de probeer-, vang- en reddingsmethoden van Elixir gebruiken.

Probeer / Rescue

De eerste in onze toolbox voor exception handling is probeer / rescue, die fouten van het gebruik vangt verhogen Dit is dus het beste geschikt voor ontwikkelaarfouten of uitzonderlijke omstandigheden zoals invoerfouten.

probeer / rescue is vergelijkbaar in gebruik met een proberen te vangen blokkering die je misschien in andere programmeertalen hebt gezien. Laten we een voorbeeld in actie bekijken:

iex> probeer het ...> raise "do failed!" ...> rescue ...> e in RuntimeError -> IO.puts ("Error:" <> e.message) ...> end Fout: mislukt! :OK

Hier gebruiken we de probeer / rescue blok en de eerder genoemde verhogen om de te vangen RuntimeError

Dit betekent het ** (RuntimeError) standaarduitvoer van verhogen wordt niet weergegeven en wordt vervangen door een mooiere uitvoer van de IO.puts telefoontje. 

Als best practice moet u de foutmelding gebruiken om de gebruiker nuttige uitvoer in gewoon Engels te geven, wat hen helpt bij het probleem. We zullen daar meer in het volgende voorbeeld naar kijken.

Meerdere fouten in een try / rescue

Een groot voordeel van Elixir is dat je meerdere uitkomsten kunt vangen in een van deze probeer / rescue blokken. Bekijk dit voorbeeld:

probeer doen opts |> Keyword.fetch! (: source_file) |> File.read! rescue e in KeyError -> IO.puts "ontbreekt: source_file optie" e in File.Error -> IO.puts "kan bronbestand niet lezen" einde

Hier hebben we twee fouten in de redden

  1. Als het bestand niet kan lezen.
  2. Als het :bron bestand symbool ontbreekt. 

Zoals eerder vermeld, kunnen we dit gebruiken voor het maken van gemakkelijk te begrijpen foutmeldingen voor onze eindgebruiker. 

Deze krachtige en minimale syntaxisbenadering van Elixir maakt het schrijven van meerdere controles zeer toegankelijk voor ons om veel mogelijke faalpunten te controleren, op een nette en beknopte manier. Dit helpt ons om ervoor te zorgen dat we geen ingewikkelde conditionals hoeven te schrijven die langdradige scripts maken die moeilijk volledig kunnen worden gevisualiseerd en die tijdens de latere ontwikkeling correct kunnen worden beschreven of waarmee een nieuwe ontwikkelaar zich kan aanmelden.

Zoals altijd bij het werken in Elixir, is KISS de beste manier om te nemen. 

Na

Er zijn situaties waarin u een specifieke actie nodig hebt die wordt uitgevoerd na het try / rescue-blok, ongeacht of er een fout is opgetreden. Voor Java- of PHP-ontwikkelaars, denk je misschien aan de probeer / catch / eindelijk of Ruby's beginnen / rescue / verzekeren.

Laten we eens kijken naar een eenvoudig voorbeeld van gebruik na.

iex> probeer het ...> raise "Ik wil met de manager praten!" ...> rescue ...> e in RuntimeError -> IO.puts ("Er is een fout opgetreden:" <> e.message) ...> after ...> IO.puts "Ongeacht wat er gebeurt, kom ik altijd op als een slechte cent." ...> einde Er is een fout opgetreden: ik wil de manager spreken! Ongeacht wat er gebeurt, kom ik altijd op als een slechte cent. :OK

Hier zie je de na wordt gebruikt om constant een bericht weer te geven (of dit zou een functie kunnen zijn die je erin zou willen gooien).

Een meer gebruikelijke praktijk die u zult gebruiken, is waar een bestand wordt gebruikt, bijvoorbeeld hier:

: ok, file = File.open "would_defo_root.jpg" probeer het eens # Probeer het bestand hierachter te benaderen # Zorg ervoor dat we achteraf opruimen. Bestand.close (bestand) einde

Werpt

Net als de verhogen en proberen te vangen methoden die we eerder hebben geschetst, hebben we ook de werp- en vangmacro's.

De ... gebruiken gooien methode verlaat de uitvoering met een specifieke waarde waarnaar we kunnen zoeken in onze vangst blokkeren en verder gebruiken zoals:

iex> probeer te doen ...> voor x <- 0… 10 do… > if x == 3, do: throw (x) ...> IO.puts (x) ...> end ...> catch ...> x -> "Caught: # x" ...> end 0 1 2 "Caught: 3"

Dus hier hebben we het vermogen om vangst alles wat we doen gooien in het try-blok. In dit geval, de voorwaardelijke als x == 3 is de trigger voor onze doen: gooien (x).

De uitvoer van de iteratie geproduceerd uit de for-lus geeft ons een duidelijk begrip van wat programmatisch is gebeurd. We zijn stap voor stap opgeschoven en de uitvoering is gestopt op de vangst.  

Vanwege deze functionaliteit kan het soms moeilijk zijn om je voor te stellen waar het gooien vangst zou worden geïmplementeerd in uw app. Een van de belangrijkste plaatsen zou zijn om een ​​bibliotheek te gebruiken waar de API niet over de juiste functionaliteit beschikt voor alle resultaten die aan de gebruiker worden gepresenteerd, en een vangst zou voldoende zijn om snel door het probleem te navigeren, in plaats van dat er veel meer in de bibliotheek moet worden verwerkt het probleem en komt er op passende wijze voor terug.

uitgangen

Eindelijk in ons Elixir foutafhandelingsarsenaal hebben we de Uitgang. Het verlaten gebeurt niet via de cadeauwinkel, maar expliciet wanneer een proces sterft. 

Uitgangen worden als volgt aangegeven:

iex> spawn_link fn -> exit ("you are done son!") end ** (EXIT from #PID<0.101.0>) "je bent klaar zoon!"

Exit-signalen worden door processen geactiveerd om een ​​van de volgende drie redenen:

  1. Een normale uitgang: Dit gebeurt wanneer een proces zijn taak heeft voltooid en de uitvoering beëindigt. Omdat deze uitgangen totaal normaal zijn, hoeft er meestal niets te gebeuren als ze zich voordoen, ongeveer zoals a exit (0) in C. De uitgangsreden voor dit soort exit is het atoom : normaal.
  2. Vanwege onverwerkte fouten: Dit gebeurt wanneer een niet-opgevangen uitzondering wordt verhoogd in het proces, zonder probeer / catch / rescue blokkeren of gooien vangen er mee omgaan.
  3. Met kracht gedood: Dit gebeurt wanneer een ander proces een exit-signaal verzendt met de reden :doden, waardoor het ontvangende proces wordt beëindigd.

Stapel sporen

Bij elk willekeurig updateschema op gooien, Uitgang of fouten, het aanroepen van de System.stacktrace zal de laatste keer terugkeren in het huidige proces. 

De stapeltracering kan nogal worden geformatteerd, maar dit is onderhevig aan wijzigingen in nieuwere versies van Elixir. Raadpleeg de handleiding voor meer informatie hierover.

Als u de stacktracering voor het huidige proces wilt retourneren, kunt u het volgende gebruiken:

Process.info (self (),: current_stacktrace)

Je eigen fouten maken

Yep, Elixir kan dat ook. Natuurlijk heb je altijd de ingebouwde typen zoals RuntimeError tot uw beschikking. Maar zou het niet fijn zijn als je nog een stap verder kunt gaan?

Het maken van uw eigen aangepaste fouttype is eenvoudig met behulp van de defexception macro, die gemakkelijk de :bericht optie om een ​​standaard foutmelding in te stellen zoals:

defmodule MyError do defexception message: "uw aangepaste fout is opgetreden" einde

Hier is hoe het te gebruiken in uw code:

iex> probeer het ...> raise MyError ...> rescue ...> e in MyError -> e ...> end% MyError message: "uw aangepaste fout is opgetreden"

Conclusie

Foutafhandeling in een meta-programmeertaal zoals Elixir heeft een hele hoop potentiële implicaties voor de manier waarop we onze applicaties ontwerpen en ze robuust genoeg maken voor het rigoureuze bashing van de productieomgeving. 

We kunnen ervoor zorgen dat de eindgebruiker altijd een idee heeft - een eenvoudige en gemakkelijk te begrijpen leidende boodschap, die hun taak niet moeilijk zal maken, maar eerder het omgekeerde. Foutmeldingen moeten altijdgeschreven zijn in gewoon Engels en veel informatie geven. Cryptische foutcodes en variabelenamen zijn niet goed voor de gemiddelde gebruiker en kunnen zelfs ontwikkelaars in verwarring brengen!

In de toekomst kunt u de uitzonderingen in uw Elixir-toepassing volgen en specifieke logboeken instellen voor bepaalde probleemgebieden, zodat u uw fix kunt analyseren en plannen, of u kunt een kant-en-klare oplossing gebruiken..

Services van derden

Verbeter de nauwkeurigheid van ons foutopsporingswerk en maak monitoring voor de stabiliteit van uw apps mogelijk met deze services van derden die beschikbaar zijn voor Elixir:

  • AppSignal kan erg nuttig zijn voor de kwaliteitsgarantiefase van de ontwikkelingscyclus. 
  • GitHub repo bugsnex is een geweldig project voor het gebruik van de API-interface met Bugsnag om defecten in uw Elixir-app verder te detecteren.
  • Bewaak uptime, systeem-RAM en fouten met HoneyBadger, die productiefoutmonitoring biedt zodat u niet op uw app hoeft te passen.

Uitbreiding van foutafhandeling verder

In de toekomst kunt u de foutafhandelingsmogelijkheden van uw app verder uitbreiden en uw code gemakkelijker leesbaar maken. Hiervoor raad ik aan om dit project te bekijken voor elegante foutafhandeling op GitHub.