Cum să se ocupe de excepții în Elixir

Gestionarea excepțiilor este o practică excelentă pentru orice metodologie de dezvoltare a software-ului. Indiferent dacă este vorba despre dezvoltarea bazată pe teste, sprinturile agile sau o sesiune de hacking cu doar o listă de bătrânețe vechi, noi toți putem beneficia de asigurarea că bazele noastre sunt acoperite cu o abordare robustă pentru manipularea defectelor. 

Este esențial să se asigure că erorile sunt luate în considerare, în timp ce este plăcut din punct de vedere estetic și, desigur, nu devine o problemă mare logic cu mesaje criptice pentru utilizatorul final de a încerca și de a culege sensul. Dacă faceți acest lucru, sunteți într-adevăr pe un drum grozav pentru a crea o aplicație solidă, stabilă și lipicioasă pe care utilizatorii le place să lucreze și va recomanda foarte mult altora.

Ideal pentru noi, Elixir oferă o gestionare a excepțiilor extinse prin intermediul mai multor mecanisme, cum ar fi încearcă să prinzi, aruncă, si : eroare, motiv tuplu.

Pentru a afișa o eroare, utilizați a ridica în shell-ul interactiv pentru a obține un prim gust:

iex> ridica "Oh noez!" ** (RuntimeError) Oh noez!

De asemenea, putem adăuga un tip la acest lucru:

iex> ridicați ArgumentError, mesaj: "Mesaj de eroare aici ..." ** (ArgumentError) mesaj de eroare aici ... 

Modul în care funcționează erorile în Elixir

Unele dintre modurile în care erorile sunt tratate în Elixir nu pot fi evidente la prima vedere.

  • În primul rând, despre procese - în utilizarea icre, putem crea procese independente. Asta înseamnă că un eșec pe un fir nu ar trebui să afecteze nici un alt proces, cu excepția cazului în care a existat o legătură într-o anumită manieră. Dar, în mod implicit, totul va rămâne stabil.
  • Pentru a notifica sistemul de eșec într-unul din aceste procese, putem folosi spawn_link macro. Aceasta este o legătură bidirecțională, ceea ce înseamnă că dacă un proces legat se va termina, va fi declanșat un semnal de ieșire.
  • Dacă semnalul de ieșire este altceva decât :normal, știm că avem o problemă. Și dacă prindem semnalul de ieșire Process.flag (: trap_exit, true), semnalul de ieșire va fi trimis în căsuța poștală a procesului, unde logica poate fi plasată pe modul de gestionare a mesajului, evitând astfel un accident dur.
  • În cele din urmă avem Monitoare, care sunt similare cu spawn_links, dar acestea sunt legături unidirecționale și le putem crea Process.monitor
  • Procesul care invocă Process.monitor vor primi mesajele de eroare la eșec.

Pentru o eroare de probă, încercați să adăugați un număr la un atom și veți obține următoarele:

iex>: foo + 69 ** (ArithmeticError) argument greșit în expresia aritmetică: erlang. + (: foo, 69)

Pentru a vă asigura că utilizatorul final nu este eronat, putem folosi metodele de încercare, captare și salvare oferite de Elixir.

Încercați / Rescue

În primul rând în caseta de instrumente pentru tratarea excepțiilor este încercați / salvare, care captează erorile produse prin utilizarea a ridica astfel încât este cu adevărat potrivită pentru erorile dezvoltatorului sau circumstanțe excepționale, cum ar fi eroarea de intrare.

încercați / salvare este similar în utilizare la a încearcă să prinzi bloc pe care l-ați văzut în alte limbi de programare. Să examinăm un exemplu în acțiune:

iex> încercați să ...> ridicați "nu a reușit!" ...> rescue ...> e în RuntimeError -> IO.puts ("Eroare:" <> e.message) ...> :O.K

Aici folosim încercați / salvare bloc și cele menționate mai sus a ridica pentru a prinde Eroare de rulare

Aceasta înseamnă ** (Eroare de rulare) outputul implicit al a ridica nu este afișată și este înlocuită cu o ieșire formatată mai bine din IO.puts apel. 

Ca o bună practică, trebuie să utilizați mesajul de eroare pentru a oferi utilizatorului o ieșire utilă în limbajul englez, ceea ce îi ajută în această problemă. O să ne uităm mai mult la exemplul următor.

Erori multiple într-o încercare / salvare

Un beneficiu major al Elixir este că puteți obține rezultate multiple în unul dintre acestea încercați / salvare blocuri. Uitați-vă la acest exemplu:

încercați să optați |> Keyword.fetch! (: source_file) |> File.read! de salvare e în KeyError -> IO.puts "lipsă: source_file opțiune" e în File.Error -> IO.puts "nu poate citi fișierul sursă" sfârșit

Aici am prins două erori în salvare

  1. Dacă fișierul nu poate citi.
  2. În cazul în care :fișier sursă simbolul lipsește. 

Așa cum am menționat anterior, putem folosi acest lucru pentru a face mesaje de eroare ușor de înțeles pentru utilizatorul nostru final. 

Această abordare puternică și minimă de sintaxă a lui Elixir face scrierea mai multor verificări foarte accesibile pentru a verifica multe puncte posibile de eșec, într-un mod curat și concis. Acest lucru ne ajută să ne asigurăm că nu avem nevoie de scrierea unor condiționări elaborate care să facă script-uri lungi care ar putea fi greu de vizualizat pe deplin și de depanare corectă în timpul dezvoltării ulterioare sau pentru ca un nou dezvoltator să se alăture.

Ca întotdeauna când lucrezi în Elixir, KISS este cea mai bună abordare pe care o poți lua. 

După

Există situații în care veți avea nevoie de o acțiune specifică efectuată după blocul de încercare / salvare, indiferent dacă a apărut o eroare. Pentru dezvoltatorii Java sau PHP, s-ar putea să vă gândiți la încercați / captură / în cele din urmă sau Ruby's începe / salvare / asigură.

Să aruncăm o privire la un exemplu simplu de utilizare după.

iex> încercați să ...> ridicați "Vreau să vorbesc cu managerul" ...> rescue ...> e în RuntimeError -> IO.puts ("A apărut o eroare:">> e.message) ...> after ...> "Indiferent de ceea ce se întâmplă, întotdeauna mă întorc ca un bănuț rău." ...> sfârșit Sa produs o eroare: Vreau să vorbesc cu managerul! Indiferent de ceea ce se întâmplă, mereu apar ca un bănuț rău. :O.K

Aici vedeți după fiind folosit pentru a face constant un mesaj de afișare (sau aceasta ar putea fi orice funcție pe care ați dorit să o aruncați acolo).

O practică mai frecventă pe care o veți găsi este folosirea unui fișier în care se accesează un fișier, de exemplu aici:

: ok, file = File.open "would_defo_root.jpg" încercați să faceți # Încercați să accesați fișierul aici după # Asigurați-vă că vom curăța ulterior File.close (file) end

aruncări

La fel de bine ca a ridica și încearcă să prinzi metodele pe care le-am subliniat mai devreme, avem, de asemenea, aruncarea și capturarea macrocomenzilor.

Utilizarea arunca metoda iese din execuție cu o valoare specifică pe care o putem căuta în cadrul nostru captură blocați și utilizați în continuare:

iex> încercați să ...> pentru x <- 0… 10 do… > dacă x == 3, facem: aruncăm (x) ...> IO.puts (x) ...> capăt ...> captura ...> x -

Așa că avem aici abilitatea captură ceva ce noi arunca în interiorul blocului de încercare. În acest caz, condiționată dacă x == 3 este declanșatorul pentru noi face: arunca (x).

Rezultatul obținut din iterația produsă din buclă pentru forță ne dă o înțelegere clară a ceea ce sa produs programat. În mod incremental am avansat și execuția a fost oprită captură.  

Din cauza acestei funcționalități, uneori este greu de imaginat unde arunca captură ar fi implementate în aplicația dvs. Un prim loc ar fi utilizarea unei biblioteci în care API nu are funcționalitate adecvată pentru toate rezultatele prezentate utilizatorului și o captură ar fi suficientă pentru a naviga rapid în jurul problemei, mai degrabă decât să se dezvolte mult mai mult în bibliotecă pentru a se ocupa problema și returnarea corespunzător pentru aceasta.

Ieșirile

În cele din urmă în arsenalul nostru de manipulare a erorilor Elixir avem Ieșire. Ieșirea se face nu prin magazinul de cadouri, dar în mod explicit ori de câte ori un proces moare. 

Ieșirile sunt semnalate astfel:

iex> spawn_link fn -> ieșire ("ați terminat fiul!") sfârșitul ** (EXIT din #PID<0.101.0>) "ați terminat fiul!"

Semnalele de ieșire sunt declanșate de procese pentru unul din următoarele trei motive:

  1. O ieșire normală: Acest lucru se întâmplă atunci când un proces și-a încheiat activitatea și se termină executarea. Deoarece aceste ieșiri sunt total normale, de obicei nu trebuie făcut nimic când se întâmplă, la fel ca a exit (0) în C. Motivul de ieșire pentru acest tip de ieșire este atomul :normal.
  2. Din cauza erorilor nefolosite: Acest lucru se întâmplă atunci când o excepție necondiționată este ridicată în interiorul procesului, cu nr încercați / captura / salvare bloc sau arunca / captură să se ocupe de ea.
  3. Forțat ucis: Acest lucru se întâmplă atunci când un alt proces trimite un semnal de ieșire cu motivul :ucide, care determină încetarea procesului de primire.

Stack Traces

La orice moment dat de actualizare arunca, Ieșire sau erori, de asteptare System.stacktrace va reveni la ultima apariție în procesul curent. 

Traseul stivei poate fi formatat destul de puțin, dar acest lucru este supus la schimbarea versiunilor mai noi ale Elixir. Pentru mai multe informații despre acest lucru, consultați pagina manuală.

Pentru a reveni la următoarea stivă pentru procesul curent, puteți utiliza următoarele:

Process.info (self (),: current_stacktrace)

Efectuarea propriilor erori

Da, Elixir poate face și asta. Desigur, aveți mereu tipurile încorporate, cum ar fi Eroare de rulare la dispozitia ta. Dar nu ar fi bine dacă ai putea merge mai departe?

Crearea propriului tip de eroare personalizat este ușoară prin utilizarea funcției defexception macro, care va accepta convenabil :mesaj , pentru a seta un mesaj de eroare implicit, cum ar fi:

defmodule MyError face mesajul defexception: "a apărut eroarea personalizată"

Iată cum se utilizează în codul dvs.:

iex> încercați să ...> ridicați MyError ...> salvare ...> e în MyError -> e ...> sfârșitul% MyError message: "eroarea personalizată a apărut"

Concluzie

Eroarea de manipulare într-un limbaj de meta-programare precum Elixir are o grămadă de implicații potențiale pentru modul în care proiectăm aplicațiile noastre și le facem suficient de robuste pentru a distruge riguros mediul de producție. 

Putem asigura că utilizatorul final este lăsat întotdeauna cu un indiciu - un mesaj de îndrumare simplu și ușor de înțeles, care nu va face ca sarcina lor să fie dificilă, ci mai degrabă inversă. Mesajele de eroare trebuie să fie întotdeaunasă fie scrise în limba engleză și să ofere o mulțime de informații. Codurile de eroare cripte și numele variabilelor nu sunt bune pentru utilizatorii obișnuiți și pot chiar confunda dezvoltatorii!

Mergând mai departe, puteți să monitorizați excepțiile ridicate în aplicația dvs. Elixir și să configurați înregistrarea specifică pentru anumite probleme, astfel încât să puteți analiza și să vă planificați soluția sau puteți să vă uitați la utilizarea unei soluții off-the-shelf.

Servicii terțe

Îmbunătățiți acuratețea lucrărilor noastre de depanare și permiteți monitorizarea stabilității aplicațiilor dvs. cu aceste servicii ale terților disponibile pentru Elixir:

  • AppSignal poate fi foarte benefic pentru faza de asigurare a calității a ciclului de dezvoltare. 
  • GitHub repo bugsnex este un proiect excelent pentru utilizarea interfeței API cu Bugsnag pentru a detecta în continuare defectele din aplicația dvs. Elixir.
  • Monitorizați timpul de funcționare, sistemul RAM și erorile cu Honeybadger, care oferă monitorizare a erorilor de producție, astfel încât să nu aveți nevoie să îngrijorați aplicația.

Extindeți modul de tratare a erorilor în continuare

Dacă mergeți mai departe, vă recomandăm să extindeți în continuare capacitățile de gestionare a erorilor ale aplicației dvs. și să faceți mai ușor citirea codului. Pentru aceasta, vă recomand să examinați acest proiect pentru o manipulare elegantă a erorilor pe GitHub. 


Cod