Umgang mit Ausnahmen in Elixir

Ausnahmebehandlung ist für jede Softwareentwicklungsmethodik eine gute Praxis. Ob testbasierte Entwicklung, agile Sprints oder eine Hacking-Session mit einer guten alten ToDo-Liste - wir alle können davon profitieren, dass unsere Grundlagen mit einem robusten Ansatz für die Fehlerbehebung abgedeckt werden. 

Es ist äußerst wichtig, sicherzustellen, dass Fehler behoben werden, während sie ästhetisch ansprechend sind und natürlich mit kryptischen Nachrichten für den Endbenutzer logischerweise kein großes Problem werden, um die Bedeutung herauszufinden. Wenn Sie das tun, sind Sie sicherlich auf einem guten Weg, eine solide, stabile und klebrige App zu erstellen, mit der Benutzer gerne arbeiten und die Sie anderen weiterempfehlen werden.

Ideal für uns bietet Elixir eine umfangreiche Ausnahmebehandlung über verschiedene Mechanismen wie z versuchen / fangen, wirft, und das : Fehler, Grund Tupel.

Um einen Fehler anzuzeigen, verwenden Sie erziehen in Ihrer interaktiven Shell, um einen ersten Eindruck zu bekommen:

iex> "oh noez!" ** (Laufzeitfehler) Oh nein!

Wir können auch so einen Typ hinzufügen:

iex> ArgumentError erhöhen, Nachricht: "Fehlermeldung hier ..." ** (ArgumentError) Fehlermeldung hier ... 

Wie funktioniert die Fehlerbehandlung in Elixir?

Einige der Vorgehensweisen, mit denen Fehler in Elixir behandelt werden, sind auf den ersten Blick nicht offensichtlich.

  • Erstens über die Verwendung von Prozessen laichen, Wir können unabhängige Prozesse schaffen. Das bedeutet, dass ein Fehler in einem Thread keinen anderen Prozess beeinträchtigen sollte, es sei denn, in irgendeiner Weise gab es eine Verknüpfung. Aber standardmäßig bleibt alles stabil.
  • Um das System über einen Fehler in einem dieser Prozesse zu benachrichtigen, können wir das verwenden spawn_link Makro. Dies ist eine bidirektionale Verbindung, dh wenn ein verknüpfter Prozess beendet wird, wird ein Beendigungssignal ausgelöst.
  • Wenn das Exit-Signal etwas anderes als ist :normal, Wir wissen, dass wir ein Problem haben. Und wenn wir das Ausgangssignal mit einfangen Process.flag (: trap_exit, true), Das Beendigungssignal wird an die Mailbox des Prozesses gesendet, wo die Logik für die Verarbeitung der Nachricht festgelegt werden kann, um einen harten Absturz zu vermeiden.
  • Schließlich haben wir Monitore, die ähnlich sind Spawn_links, Dies sind jedoch unidirektionale Links, mit denen wir sie erstellen können Prozessmonitor
  • Der Prozess, der das anruft Prozessmonitor erhält die Fehlermeldung bei einem Fehler.

Um einen Beispielfehler zu erhalten, fügen Sie einem Atom eine Zahl hinzu und Sie erhalten Folgendes:

iex>: foo + 69 ** (ArithmeticError) falsches Argument im arithmetischen Ausdruck: erlang. + (: foo, 69)

Um sicherzustellen, dass der Endbenutzer nicht fehlerbehaftet wird, können wir die von Elixir bereitgestellten Try-, Catch- und Rettungsmethoden verwenden.

Versuch / Rettung

Zunächst in unserer Toolbox für Ausnahmebehandlung versuchen / retten, welche durch die Verwendung von erziehen ist also am besten für Entwicklerfehler oder außergewöhnliche Umstände wie Eingabefehler geeignet.

versuchen / retten ist in der Verwendung a ähnlich versuchen / fangen Block, den Sie möglicherweise in anderen Programmiersprachen gesehen haben. Schauen wir uns ein Beispiel in Aktion an:

iex> try do…> "do failed!" erhöhen ...> rescue ...> e in RuntimeError -> IO.puts ("Error:" <> e.message) ...> Ende Fehler: do failed! :OK

Hier nutzen wir die versuchen / retten Block und die zuvor genannten erziehen das zu fangen Laufzeit Fehler

Das bedeutet das ** (Laufzeit Fehler) Standardausgabe von erziehen wird nicht angezeigt und durch eine schönere formatierte Ausgabe aus der ersetzt IO.puts Anruf. 

Als bewährte Methode müssen Sie die Fehlermeldung verwenden, um dem Benutzer eine nützliche Ausgabe in Englisch zu geben, die ihm bei der Problemlösung hilft. Wir werden das im nächsten Beispiel genauer betrachten.

Mehrere Fehler in einem Versuch / einer Rettung

Ein großer Vorteil von Elixir ist, dass Sie in einem davon mehrere Ergebnisse erzielen können versuchen / retten Blöcke. Schau dir dieses Beispiel an:

probieren Sie die Optionen |> Keyword.fetch! (: source_file) |> File.read! rescue e in KeyError -> IO.puts "fehlt: source_file-Option" e in File.Error -> IO.puts "Quelldatei kann nicht gelesen werden" ende

Hier haben wir zwei Fehler in der Rettung

  1. Wenn die Datei nicht lesen kann.
  2. Wenn die : Quelldatei Symbol fehlt. 

Wie bereits erwähnt, können wir dies verwenden, um dem Endbenutzer leicht verständliche Fehlermeldungen zu geben. 

Dieser leistungsstarke und minimale Syntaxansatz von Elixir macht das Schreiben mehrerer Überprüfungen für uns sehr leicht zugänglich, um viele mögliche Fehlerquellen auf eine übersichtliche und prägnante Weise zu überprüfen. Auf diese Weise können wir sicherstellen, dass wir keine ausführlichen Bedingungen schreiben müssen, die langwierige Skripts erstellen, die möglicherweise nur schwer vollständig visualisiert werden können und die während der späteren Entwicklung oder für einen neuen Entwickler korrekt debuggen können.

Wie immer, wenn Sie in Elixir arbeiten, ist KISS der beste Ansatz. 

Nach dem

Es gibt Situationen, in denen Sie nach dem try / rescue-Block eine bestimmte Aktion ausführen müssen, unabhängig davon, ob ein Fehler aufgetreten ist. Für Java- oder PHP-Entwickler denken Sie möglicherweise an die versuchen / fangen / endlich oder Ruby beginnen / retten / sicherstellen.

Schauen wir uns ein einfaches Beispiel für die Verwendung an nach dem.

iex> try do…> hebe "Ich möchte mit dem Manager sprechen!" ...> retten ...> e in RuntimeError -> IO.puts ("Ein Fehler ist aufgetreten:" <> e.message) ...> nach…> IO.puts "Egal was passiert, ich tauche immer wie ein böser Penny auf." ...> end Es ist ein Fehler aufgetreten: Ich möchte mit dem Manager sprechen! Egal was passiert, ich tauche immer wie ein böser Penny auf. :OK

Hier sehen Sie die nach dem Wird verwendet, um ständig eine Nachricht anzuzeigen (oder dies kann jede Funktion sein, die Sie dort hineinwerfen möchten).

Eine üblichere Praxis, bei der Sie darauf zurückgreifen werden, ist der Zugriff auf eine Datei, beispielsweise hier:

: ok, file = File.open "would_defo_root.jpg" try do # Versuchen Sie, auf die Datei hier nach # zuzugreifen. Stellen Sie sicher, dass wir danach File.close (file) enden

Wirft

Ebenso wie erziehen und versuchen / fangen Methoden, die wir zuvor beschrieben haben, haben wir auch die Wurf- und Fangmakros.

Verwendung der werfen Methode beendet die Ausführung mit einem bestimmten Wert, nach dem wir in unserem suchen können Fang blockieren und weiter wie folgt verwenden:

iex> try do…> für x <- 0… 10 do… > Wenn x == 3, mache: throw (x)…> IO.puts (x)…> Ende…> catch…> x -> "Gefangen: # x"…> Ende 0 1 2 "Gefangen: 3"

Also hier haben wir die Fähigkeit zu Fang irgendetwas wir werfen innerhalb des try-Blocks. In diesem Fall die Bedingung wenn x == 3 ist der Auslöser für unsere mach: werfen (x).

Die Ausgabe der Iteration, die von der for-Schleife erzeugt wird, gibt uns ein klares Verständnis dafür, was programmgesteuert aufgetreten ist. Inkrementell haben wir einen Schritt nach vorne gemacht, und die Hinrichtung wurde am gestoppt Fang.  

Aufgrund dieser Funktionalität kann es manchmal schwierig sein, sich ein Bild davon zu machen werfen Fang wäre in Ihrer App implementiert. Ein Hauptplatz wäre die Verwendung einer Bibliothek, bei der die API nicht für alle dem Benutzer präsentierten Ergebnisse über eine angemessene Funktionalität verfügt, und ein Haken würde ausreichen, um schnell durch das Problem zu navigieren, anstatt sich innerhalb der Bibliothek sehr viel weiterentwickeln zu müssen die Ausgabe und kehren Sie entsprechend zurück.

Ausgänge

Schließlich haben wir in unserem Elixir Error Handling Arsenal das Ausfahrt. Das Beenden erfolgt nicht über den Geschenkeladen, sondern explizit, wenn ein Prozess stirbt. 

Ausgänge werden wie folgt signalisiert:

iex> spawn_link fn -> exit ("Du bist fertig, Sohn!") end ** (EXIT von #PID.)<0.101.0>) "Du bist fertig, Sohn!"

Ausgangssignale werden von Prozessen aus einem der folgenden drei Gründe ausgelöst:

  1. Ein normaler AusgangDies geschieht, wenn ein Prozess seinen Job abgeschlossen hat und die Ausführung beendet ist. Da diese Ausgänge völlig normal sind, muss in der Regel nichts getan werden, ähnlich wie ein Ausfahrt (0) in C. Der Ausgangsgrund für diese Art des Ausgangs ist das Atom :normal.
  2. Wegen nicht behandelter Fehler: Dies geschieht, wenn im Prozess eine nicht erfasste Ausnahme mit Nein ausgelöst wird versuchen / fangen / retten blockieren oder werfen / fangen damit klar kommen.
  3. Mit Gewalt getötet: Dies geschieht, wenn ein anderer Prozess ein Beendigungssignal mit dem Grund sendet :töten, was den Empfangsprozess zum Abschluss zwingt.

Spuren stapeln

Zu jedem Update-Zeitpunkt am werfen, Ausfahrt oder Fehler, die anrufen System.stacktrace gibt das letzte Vorkommen im aktuellen Prozess zurück. 

Der Stack-Trace kann ziemlich formatiert werden, dies kann sich jedoch in neueren Elixir-Versionen ändern. Weitere Informationen hierzu finden Sie auf der Handbuchseite.

Um den Stack-Trace für den aktuellen Prozess zurückzugeben, können Sie Folgendes verwenden:

Process.info (self (),: current_stacktrace)

Eigene Fehler machen

Ja, Elixir kann das auch. Natürlich haben Sie immer die eingebauten Typen wie Laufzeit Fehler zu deiner Verfügung. Aber wäre es nicht schön, wenn Sie noch einen Schritt weiter gehen könnten??

Das Erstellen Ihres eigenen benutzerdefinierten Fehlertyps ist mit der Enttäuschung Makro, das bequem das akzeptiert :Botschaft Option, um eine Standardfehlermeldung wie folgt festzulegen:

defmodule MyError do defexception message: "Ihr benutzerdefinierter Fehler ist aufgetreten" ende

So verwenden Sie es in Ihrem Code:

iex> try do…> myError erhöhen…> retten…> e in MyError -> e…> end% MyError Nachricht: "Ihr benutzerdefinierter Fehler ist aufgetreten"

Fazit

Die Fehlerbehandlung in einer Meta-Programmiersprache wie Elixir hat eine ganze Reihe potenzieller Auswirkungen darauf, wie wir unsere Anwendungen entwerfen und robust genug machen, um die Produktionsumgebung streng zu beherrschen. 

Wir können sicherstellen, dass dem Endbenutzer immer ein Hinweis bleibt - eine einfache und leicht verständliche Leitmitteilung, die ihre Aufgabe nicht erschwert, sondern eher umgekehrt. Fehlermeldungen müssen immer seinin reinem Englisch verfasst sein und viele Informationen geben. Kryptische Fehlercodes und Variablennamen sind für durchschnittliche Benutzer nicht geeignet und können Entwickler sogar verwirren!

In der Zukunft können Sie die in Ihrer Elixir-Anwendung angeführten Ausnahmen überwachen und eine bestimmte Protokollierung für bestimmte Problembereiche einrichten, sodass Sie die Fehlerbehebung analysieren und planen oder eine Standardlösung verwenden können.

Drittanbieter-Services

Verbessern Sie die Genauigkeit unserer Debugging-Arbeit und ermöglichen Sie die Überwachung der Stabilität Ihrer Apps mit diesen für Elixir verfügbaren Drittanbieter-Services:

  • AppSignal kann für die Qualitätssicherungsphase des Entwicklungszyklus sehr vorteilhaft sein. 
  • GitHub Repo Bugsnex ist ein großartiges Projekt für die Verwendung der API-Schnittstelle mit Bugsnag zur weiteren Erkennung von Fehlern in Ihrer Elixir-App.
  • Überwachen Sie die Betriebszeit, den System-RAM und Fehler mit Honeybadger, das Produktionsfehlerüberwachung bietet, sodass Sie Ihre App nicht babysitten müssen.

Fehlerbehandlung weiter erweitern

In Zukunft möchten Sie möglicherweise die Fehlerbehebungsfunktionen Ihrer App erweitern und den Code lesbarer machen. Ich empfehle Ihnen, dieses Projekt für eine elegante Fehlerbehandlung auf GitHub zu überprüfen.