Come gestire le eccezioni in elisir

La gestione delle eccezioni è un'ottima pratica per qualsiasi metodologia di sviluppo del software. Che si tratti di uno sviluppo basato su test, di sprint agili o di una sessione di hacking con solo una buona vecchia lista di cose da fare, tutti possiamo trarre vantaggio dall'assicurare che le nostre basi siano coperte da un solido approccio alla gestione dei difetti. 

È fondamentale assicurarsi che gli errori siano curati, pur essendo esteticamente gradevoli e, naturalmente, non si trasformino in un grosso problema logicamente con messaggi criptici per l'utente finale che cerca di carpire il significato. Se lo fai, sei sicuramente sulla buona strada per creare un'app solida, stabile e appiccicosa con cui gli utenti si divertono a lavorare e che consiglierà molto agli altri.

Idealmente per noi, Elixir fornisce un'eccezionale gestione delle eccezioni tramite diversi meccanismi come prova a prendere, getta, e il : errore, motivo tuple.

Per visualizzare un errore, utilizzare aumentare nella tua shell interattiva per ottenere un primo assaggio:

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

Possiamo anche aggiungere un tipo a questo in questo modo:

iex> raise ArgumentError, message: "messaggio di errore qui ..." ** (ArgumentError) messaggio di errore qui ... 

Come funziona la gestione degli errori in elisir

Alcuni dei modi in cui gli errori sono trattati in elisir possono non essere evidenti a prima vista.

  • In primo luogo sui processi in uso uova, possiamo creare processi indipendenti. Ciò significa che un errore su un thread non dovrebbe influire su nessun altro processo, a meno che non ci fosse un collegamento in qualche modo. Ma per impostazione predefinita, tutto rimarrà stabile.
  • Per notificare al sistema un errore in uno di questi processi, possiamo usare il spawn_link macro. Questo è un collegamento bidirezionale, il che significa che se un processo collegato termina, verrà attivato un segnale di uscita.
  • Se il segnale di uscita è diverso da :normale, sappiamo che abbiamo un problema E se intercettiamo il segnale di uscita con Process.flag (: trap_exit, true), il segnale di uscita verrà inviato alla casella di posta del processo, in cui è possibile posizionare la logica su come gestire il messaggio, evitando così un arresto anomalo.
  • Finalmente abbiamo i monitor, che sono simili a spawn_links, ma questi sono collegamenti unidirezionali e possiamo crearli con Process.monitor
  • Il processo che invoca il Process.monitor riceverà i messaggi di errore in caso di errore.

Per un errore di esempio, prova ad aggiungere un numero a un atomo e otterrai quanto segue:

iex>: foo + 69 ** (ArithmeticError) Argomento non valido nell'espressione aritmetica: erlang. + (: foo, 69)

Per garantire che l'utente finale non diventi errato, possiamo utilizzare i metodi try, catch e rescue forniti da Elixir.

Prova / Rescue

Il primo nella nostra casella degli strumenti per la gestione delle eccezioni è provare / salvataggio, che cattura gli errori prodotti usando aumentare quindi è davvero il più adatto per errori dello sviluppatore, o circostanze eccezionali come l'errore di input.

provare / salvataggio è simile nell'uso a a prova a prendere blocco che potresti aver visto in altri linguaggi di programmazione. Diamo un'occhiata a un esempio in azione:

iex> try do ...> raise "do failed!" ...> rescue ...> e in RuntimeError -> IO.puts ("Errore:" <> e.message) ...> end Error: do failed! :ok

Qui utilizziamo il provare / salvataggio blocco e il suddetto aumentare prendere il RuntimeError

Questo significa ** (RuntimeError) uscita predefinita di aumentare non viene visualizzato e viene sostituito da un output formattato più bello da IO.puts chiamata. 

Come best practice, è necessario utilizzare il messaggio di errore per fornire all'utente un output utile in inglese semplice, che li aiuta a risolvere il problema. Lo vedremo di più nel prossimo esempio.

Errori multipli in una prova / salvataggio

Uno dei principali vantaggi di Elixir è che puoi prendere più risultati in uno di questi provare / salvataggio blocchi. Guarda questo esempio:

prova opts |> Keyword.fetch! (: source_file) |> File.read! rescue e in KeyError -> IO.puts "mancante: opzione source_file" e in File.Error -> IO.puts "impossibile leggere il file sorgente" fine

Qui abbiamo riscontrato due errori nel salvare

  1. Se il file non è in grado di leggere.
  2. Se la :file sorgente manca il simbolo. 

Come accennato in precedenza, possiamo usarlo per creare messaggi di errore di facile comprensione per il nostro utente finale. 

Questo approccio sintattico potente e minimale di Elixir rende molto accessibile la scrittura di più assegni per controllare molti possibili punti di errore, in modo accurato e conciso. Questo ci aiuta ad assicurarci che non abbiamo bisogno di scrivere condizionali elaborati per creare script prolissi che potrebbero essere difficili da visualizzare completamente e debuggare correttamente durante lo sviluppo successivo o per un nuovo sviluppatore.

Come sempre quando si lavora in Elixir, KISS è l'approccio migliore da adottare. 

Dopo

Esistono situazioni in cui è necessario eseguire un'azione specifica dopo il blocco try / rescue, indipendentemente dal fatto che si sia verificato un errore. Per gli sviluppatori Java o PHP, potresti pensare al try / catch / finally o di Ruby iniziare / rescue / garantire.

Diamo un'occhiata a un semplice esempio di utilizzo dopo.

iex> try do ...> raise "Voglio parlare con il manager!" ...> rescue ...> e in RuntimeError -> IO.puts ("Si è verificato un errore:" <> e.message) ...> after ...> IO.puts "Indipendentemente da quello che succede, vengo sempre come un brutto penny." ...> fine Si è verificato un errore: voglio parlare con il manager! Indipendentemente da quello che succede, vengo sempre come un brutto penny. :ok

Qui vedi il dopo essere utilizzato per visualizzare costantemente un messaggio (o questa potrebbe essere una funzione che si desidera inserire).

Una pratica più comune su cui si trova questo utilizzo è dove si accede a un file, ad esempio qui:

: ok, file = File.open "would_defo_root.jpg" prova # Prova ad accedere al file qui dopo # Assicurati di ripulire dopo File.close (file) fine

Genera

Così come il aumentare e prova a prendere metodi che abbiamo delineato in precedenza, abbiamo anche i macro di lancio e cattura.

Usando il gettare il metodo esce dall'esecuzione con un valore specifico che possiamo cercare nel nostro catturare bloccare e utilizzare ulteriormente in questo modo:

iex> prova ...> per x <- 0… 10 do… > se x == 3, do: throw (x) ...> IO.puts (x) ...> end ...> catch ...> x -> "Catturato: # x" ...> fine 0 1 2 "Catturato: 3"

Quindi qui abbiamo la capacità di catturare qualsiasi cosa noi gettare all'interno del blocco try. In questo caso, il condizionale se x == 3 è l'innesco per il nostro do: throw (x).

L'output dall'iterazione prodotta dal ciclo for ci fornisce una chiara comprensione di ciò che si è verificato a livello di codice. Incrementalmente ci siamo fatti avanti e l'esecuzione è stata interrotta catturare.  

A causa di questa funzionalità, a volte può essere difficile immaginare dove gettare catturare sarebbe implementato nella tua app. Un posto privilegiato sarebbe l'uso di una libreria in cui l'API non ha funzionalità adeguate per tutti i risultati presentati all'utente, e una cattura sarebbe sufficiente per navigare rapidamente attorno al problema, piuttosto che dover sviluppare molto di più all'interno della libreria per gestire il problema e tornare in modo appropriato per questo.

Esce

Infine, nel nostro errore di elisir che gestisce l'arsenale, abbiamo il Uscita. L'uscita viene effettuata non attraverso il negozio di articoli da regalo, ma esplicitamente ogni volta che un processo muore. 

Le uscite sono segnalate in questo modo:

iex> spawn_link fn -> exit ("hai finito figlio!") fine ** (ESCI da #PID<0.101.0>) "hai finito figlio!"

I segnali di uscita vengono attivati ​​dai processi per uno dei seguenti tre motivi:

  1. Un'uscita normale: Questo accade quando un processo ha completato il suo lavoro e termina l'esecuzione. Dal momento che queste uscite sono totalmente normali, di solito non è necessario fare nulla quando accadono, proprio come a exit (0) in C. Il motivo dell'uscita per questo tipo di uscita è l'atomo :normale.
  2. A causa di errori non gestiti: Questo accade quando viene sollevata un'eccezione non rilevata all'interno del processo, con no try / catch / salvataggio blocco o tiro / catch occuparsene.
  3. Ucciso con la forza: Questo accade quando un altro processo invia un segnale di uscita con il motivo :uccidere, che costringe il processo di ricezione a terminare.

Stack Traces

In qualsiasi data di aggiornamento, il collegamento è attivo gettare, Uscita o errori, chiamando il System.stacktrace restituirà l'ultima occorrenza nel processo corrente. 

La traccia dello stack può essere formattata un po ', ma questo è soggetto a modifiche nelle versioni più recenti di Elixir. Per ulteriori informazioni su questo, si prega di fare riferimento alla pagina di manuale.

Per restituire la traccia dello stack per il processo corrente, è possibile utilizzare quanto segue:

Process.info (self (),: current_stacktrace)

Crea i tuoi errori

Sì, anche l'elisir può farlo. Ovviamente, hai sempre i tipi built-in come RuntimeError A tua disposizione. Ma non sarebbe bello se tu potessi fare un ulteriore passo avanti?

Creare il proprio tipo di errore personalizzato è facile usando il defexception macro, che accetterà comodamente il :Messaggio opzione, per impostare un messaggio di errore predefinito in questo modo:

defmodule MyError fa messaggio di defexception: "il tuo errore personalizzato si è verificato" fine

Ecco come usarlo nel tuo codice:

iex> try do ...> raise MyError ...> rescue ...> e in MyError -> e ...> end% MyError message: "il tuo errore personalizzato si è verificato"

Conclusione

La gestione degli errori in un linguaggio di meta-programmazione come Elixir ha un mucchio di potenziali implicazioni per il modo in cui progettiamo le nostre applicazioni e le rendiamo abbastanza robuste per il rigoroso attacco all'ambiente di produzione. 

Possiamo garantire che l'utente finale abbia sempre un indizio: un messaggio guida semplice e facile da capire, che non renderà difficile il loro compito, ma piuttosto l'inverso. I messaggi di errore devono sempreessere scritto in inglese e dare molte informazioni. Codici di errore criptici e nomi di variabili non sono buoni per gli utenti medi e possono anche confondere gli sviluppatori!

In futuro, è possibile monitorare le eccezioni sollevate nell'applicazione Elixir e impostare la registrazione specifica per determinati punti problematici, in modo da poter analizzare e pianificare la correzione, oppure è possibile esaminare utilizzando una soluzione pronta per l'uso..

Servizi di terze parti

Migliora l'accuratezza del nostro lavoro di debug e abilita il monitoraggio della stabilità delle tue app con questi servizi di terze parti disponibili per Elixir:

  • AppSignal può essere molto utile per la fase di assicurazione della qualità del ciclo di sviluppo. 
  • GitHub repo bugsnex è un ottimo progetto per l'utilizzo dell'interfaccia API con Bugsnag per rilevare ulteriormente i difetti nell'app Elixir.
  • Monitora uptime, RAM di sistema ed errori con Honeybadger, che fornisce il monitoraggio degli errori di produzione in modo che non sia necessario fare da babysitter alla tua app.

Estensione della gestione degli errori Ulteriori

In futuro, potresti voler estendere ulteriormente le capacità di gestione degli errori della tua app e rendere il tuo codice più facile da leggere. Per questo, ti consiglio di dare un'occhiata a questo progetto per l'elegante gestione degli errori su GitHub.