Elisir - Gestione degli errori

Elixir ha tre meccanismi di errore: errori, lanci ed uscite. Esploriamo ogni meccanismo in dettaglio.

Errore

Gli errori (o le eccezioni) vengono utilizzati quando nel codice si verificano cose eccezionali. Un errore di esempio può essere recuperato provando ad aggiungere un numero in una stringa -

IO.puts(1 + "Hello")

Quando il programma sopra viene eseguito, produce il seguente errore:

** (ArithmeticError) bad argument in arithmetic expression
   :erlang.+(1, "Hello")

Questo era un errore integrato di esempio.

Segnalazione di errori

Noi possiamo raiseerrori utilizzando le funzioni di sollevamento. Consideriamo un esempio per capire lo stesso:

#Runtime Error with just a message
raise "oops"  # ** (RuntimeError) oops

Altri errori possono essere generati con raise / 2 passando il nome dell'errore e un elenco di argomenti di parole chiave

#Other error type with a message
raise ArgumentError, message: "invalid argument foo"

Puoi anche definire i tuoi errori e sollevarli. Considera il seguente esempio:

defmodule MyError do
   defexception message: "default message"
end

raise MyError  # Raises error with default message
raise MyError, message: "custom message"  # Raises error with custom message

Salvataggio degli errori

Non vogliamo che i nostri programmi si chiudano improvvisamente, ma piuttosto gli errori devono essere gestiti con attenzione. Per questo usiamo la gestione degli errori. Noirescue errori utilizzando il try/rescuecostruire. Consideriamo il seguente esempio per capire lo stesso:

err = try do
   raise "oops"
rescue
   e in RuntimeError -> e
end

IO.puts(err.message)

Quando il programma di cui sopra viene eseguito, produce il seguente risultato:

oops

Abbiamo gestito gli errori nell'istruzione rescue utilizzando il pattern matching. Se non abbiamo alcun uso dell'errore e vogliamo solo usarlo a scopo di identificazione, possiamo anche usare il modulo -

err = try do
   1 + "Hello"
rescue
   RuntimeError -> "You've got a runtime error!"
   ArithmeticError -> "You've got a Argument error!"
end

IO.puts(err)

Quando si esegue il programma sopra, produce il seguente risultato:

You've got a Argument error!

NOTE- La maggior parte delle funzioni nella libreria standard Elixir sono implementate due volte, una volta restituendo tuple e l'altra volta generando errori. Ad esempio, il fileFile.read e il File.read!funzioni. Il primo restituiva una tupla se il file è stato letto con successo e se si è verificato un errore, questa tupla è stata utilizzata per fornire il motivo dell'errore. Il secondo ha generato un errore se si è verificato un errore.

Se usiamo il primo approccio di funzione, allora dobbiamo usare il caso per il pattern che corrisponde all'errore e agire in base a quello. Nel secondo caso, utilizziamo l'approccio try rescue per il codice soggetto a errori e gestiamo gli errori di conseguenza.

Lancia

In Elixir, un valore può essere lanciato e successivamente essere catturato. Lancio e presa sono riservati alle situazioni in cui non è possibile recuperare un valore se non utilizzando lancio e ripresa.

Le istanze sono abbastanza rare nella pratica tranne quando si interfaccia con le librerie. Ad esempio, supponiamo ora che il modulo Enum non fornisse alcuna API per trovare un valore e che avessimo bisogno di trovare il primo multiplo di 13 in un elenco di numeri -

val = try do
   Enum.each 20..100, fn(x) ->
      if rem(x, 13) == 0, do: throw(x)
   end
   "Got nothing"
catch
   x -> "Got #{x}"
end

IO.puts(val)

Quando il programma di cui sopra viene eseguito, produce il seguente risultato:

Got 26

Uscita

Quando un processo muore per "cause naturali" (ad esempio, eccezioni non gestite), invia un segnale di uscita. Un processo può anche morire inviando esplicitamente un segnale di uscita. Consideriamo il seguente esempio:

spawn_link fn -> exit(1) end

Nell'esempio sopra, il processo collegato è morto inviando un segnale di uscita con valore 1. Notare che l'uscita può anche essere "catturata" usando try / catch. Ad esempio:

val = try do
   exit "I am exiting"
catch
   :exit, _ -> "not really"
end

IO.puts(val)

Quando il programma di cui sopra viene eseguito, produce il seguente risultato:

not really

Dopo

A volte è necessario assicurarsi che una risorsa venga ripulita dopo un'azione che può potenzialmente generare un errore. Il costrutto try / after ti consente di farlo. Ad esempio, possiamo aprire un file e utilizzare una clausola after per chiuderlo, anche se qualcosa va storto.

{:ok, file} = File.open "sample", [:utf8, :write]
try do
   IO.write file, "olá"
   raise "oops, something went wrong"
after
   File.close(file)
end

Quando eseguiamo questo programma, ci darà un errore. Ma ilafter assicurerà che il descrittore di file venga chiuso in caso di tali eventi.