Tracks
/
Elixir
Elixir
/
Übungen
/
UPN-Rechner
UPN-Rechner

UPN-Rechner

Lernübung

Einführung

Fehler

Fehler passieren. In Elixir heißt es zwar oft „let it crash", aber manchmal müssen wir den Funktionsaufruf abfangen und in einen bekannten guten Zustand zurückversetzen, um einen Software-Vertrag zu erfüllen. In manchen Sprachen werden Fehler als Mittel zur Steuerung des Programmflusses eingesetzt, in Elixir wird dieses Muster jedoch nicht empfohlen. Oft erkennen wir Funktionen, die einen Fehler auslösen können, schon an ihrem Namen: Funktionen, die Fehler auslösen, haben ein ! am Ende ihres Namens. Das steht im Gegensatz zu Funktionen, die {:ok, value} oder :error zurückgeben. Schau dir diese Beispiele aus der Bibliothek an:

Map.fetch(%{a: 1}, :b)
# => :error
Map.fetch!(%{a: 1}, :b)
# => raises KeyError

Try/Rescue

Elixir bietet ein Konstrukt, um mit try .. rescue Fehler abzufangen.

try do
  raise RuntimeError, "error"
rescue
  e in RuntimeError -> :error
end

Schauen wir uns dieses Konstrukt genauer an:

  • Zeile 1: Der Block wird mit try deklariert.
  • Zeile 2: Hier steht der Funktionsaufruf, der einen Fehler auslösen kann, in diesem Fall rufen wir raise/2 auf.
  • Zeile 4: Im rescue-Abschnitt gleichen wir den Modulnamen des ausgelösten Fehlers per Pattern-Matching ab
    • auf der linken Seite von ->:
      • e wird mit dem Fehler-Struct abgeglichen.
      • in ist ein Schlüsselwort.
      • RuntimeError ist der Fehler, den wir abfangen wollen.
        • Wenn wir alle Fehler abfangen wollten, könnten wir _ statt des Modulnamens verwenden oder das Schlüsselwort in ganz weglassen.
    • auf der rechten Seite:
      • die Anweisungen, die ausgeführt werden, wenn der Fehler übereinstimmt.

Fehler-Structs

Fehler (manchmal auch „Exceptions" genannt), die du auf diese Weise abfängst, sind Structs. Fehler werden in Elixir nur sehr selten abgefangen. Normalerweise wird der abgefangene Fehler protokolliert oder an einen externen Monitoring-Dienst gesendet und dann erneut ausgelöst. Das bedeutet, dass uns die interne Struktur des jeweiligen Fehler-Structs normalerweise nicht interessiert.

Im Konzept Exceptions erfährst du mehr über Fehler-Structs, auch wie du einen eigenen, selbst definierten Fehler anlegst.

Anleitung

Während du bei Instruments of Texas arbeitest, sollst du an einem experimentellen Rechner für die umgekehrte polnische Notation [RPN] arbeiten, der in Elixir geschrieben ist. Dein Team hat ein Problem damit, dass einige Operationen Fehler auslösen und den Prozess zum Absturz bringen. Du sollst eine Funktion schreiben, die die Operationsfunktion verpackt, damit die Fehler mit idiomatischem Elixir-Code eleganter behandelt werden können.

1. Warne das Team

Implementiere die Funktion calculate!/2, um die Operationsfunktion mit dem Stack als einzigem Argument aufzurufen. Die Operationsfunktion ist woanders definiert, aber du weißt, dass sie entweder erfolgreich abgeschlossen wird oder einen Fehler auslöst.

stack = []
operation = fn _ -> :ok end
RPNCalculator.calculate!(stack, operation)
# => :ok

stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate!(stack, operation)
# => ** (ArgumentError) An error occurred

Funktionsnamen, die auf ! enden, sind eine Warnung an Programmierer, dass diese Funktion einen Fehler auslösen kann

2. Verpacke den Fehler

Bei weiterer Recherche fällt dir auf, dass viele Funktionen Atome und Tupel verwenden, um ihren Erfolg oder Misserfolg anzuzeigen. Implementiere calculate/2 mit dieser Strategie.

stack = []
operation = fn _ -> "operation completed" end
RPNCalculator.calculate(stack, operation)
# => {:ok, "operation completed"}

stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate(stack, operation)
# => :error

3. Gib die Nachricht weiter

Einige der Fehler enthalten wichtige Informationen, die deine Kollegen brauchen, um den korrekten Betrieb des Systems sicherzustellen. Implementiere calculate_verbose/2, um die Fehlermeldung weiterzugeben. Der Fehler ist ein Struct, das ein Feld :message hat.

stack = []
operation = fn _ -> "operation completed" end
RPNCalculator.calculate_verbose(stack, operation)
# => {:ok, "operation completed"}

stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate_verbose(stack, operation)
# => {:error, "An error occurred"}
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
Elixir Exercism

Bereit, mit UPN-Rechner zu starten?

Melde dich bei Exercism an, um Elixir mit 58 Konzepte168 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.