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
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:
try deklariert.raise/2 auf.rescue-Abschnitt gleichen wir den Modulnamen des ausgelösten Fehlers per Pattern-Matching ab
->:
e wird mit dem Fehler-Struct abgeglichen.in ist ein Schlüsselwort.RuntimeError ist der Fehler, den wir abfangen wollen.
_ statt des Modulnamens verwenden oder das Schlüsselwort in ganz weglassen.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.
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.
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
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
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"}
Melde dich bei Exercism an, um Elixir mit 58 Konzepte168 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.