Blog

Versuch, Option oder beides?

Remco Beckers

Remco Beckers

Aktualisiert Oktober 22, 2025
8 Minuten

Scala bietet viele verschiedene Optionen für die Behandlung und Meldung von Fehlern, so dass es schwierig sein kann, zu entscheiden, welche für Ihre Situation am besten geeignet ist. In Scala und funktionalen Programmiersprachen ist es üblich, die Fehler, die auftreten können, explizit in der Funktionssignatur (d.h. im Rückgabetyp) anzugeben, im Gegensatz zur gängigen Praxis in anderen Programmiersprachen, wo entweder spezielle Werte verwendet werden (-1 für eine fehlgeschlagene Suche?) oder eine Ausnahme ausgelöst wird. Gehen wir die wichtigsten Optionen durch, die Sie als Scala-Entwickler haben, und sehen wir, wann Sie was verwenden sollten!

Option Eine besondere Art von Fehler, der auftreten kann, ist das Fehlen eines Wertes. Wenn Sie zum Beispiel einen Wert in einer Datenbank oder einer Liste suchen, können Sie die Find-Methode verwenden. Wenn Sie dies in Java implementieren, besteht die übliche Lösung (zumindest bis Java 7) darin, null zurückzugeben, wenn ein Wert nicht gefunden werden kann, oder eine Version der NotFound-Ausnahme zu werfen. In Scala verwenden Sie in der Regel den Typ Option[T], der Some(value) zurückgibt, wenn der Wert gefunden wurde, und None, wenn der Wert nicht vorhanden ist. Sie müssen also nicht im Javadoc oder Scaladoc nachsehen, sondern nur im Typ der Funktion, um zu wissen, wie ein fehlender Wert dargestellt wird. Außerdem müssen Sie Ihren Code nicht mit Null-Prüfungen oder try/catch-Blöcken überfrachten. Ein weiterer Anwendungsfall ist das Parsen von Eingabedaten: Benutzereingaben, JSON, XML usw.. Anstatt eine Exception für ungültige Eingaben auszulösen, geben Sie einfach ein None zurück, um anzuzeigen, dass das Parsen fehlgeschlagen ist. Der Nachteil der Verwendung von Option für diese Situation ist, dass Sie die Art des Fehlers vor dem Benutzer Ihrer Funktion verbergen, was je nach Anwendungsfall ein Problem sein kann oder auch nicht. Wenn diese Informationen wichtig sind, lesen Sie die nächsten Abschnitte. Ein Beispiel, das sicherstellt, dass ein Name nicht leer ist: [scala] def validateName(name: String): Option[String] = { if (name.isEmpty) None else Some(name) } [/scala] Sie können die Methode validateName auf verschiedene Weise in Ihrem Code verwenden: [scala] // Einen Standardwert verwenden validateName(inputName).getOrElse("Standardname") // Eine andere Funktion auf das Ergebnis anwenden validateName(inputName).map(.toUpperCase) // Mit anderen Überprüfungen kombinieren und beim ersten Fehler kurzschließen // Eine neue Option[Person] zurückgeben for { name <- validateName(inputName) age <- validateAge(inputAge) } yield Person(name, age) [/scala] Entweder Option ist gut geeignet, um einen Fehler anzuzeigen, aber wenn Sie weitere Informationen über den Fehler benötigen, ist Option nicht leistungsfähig genug. In diesem Fall kann Either[L,R] verwendet werden. Es gibt 2 Implementierungen, Left und Right. Beide können einen benutzerdefinierten Typ verpacken, d.h. Typ L und Typ R. Gemäß der Konvention ist Right rechts, d.h. es enthält das erfolgreiche Ergebnis und Left enthält den Fehler. Wenn Sie die validateName-Methode so umschreiben, dass sie eine Fehlermeldung zurückgibt, ergibt sich Folgendes: [scala] def validateName(name: String): Either[String, String] = { if (name.isEmpty) Left("Name darf nicht leer sein") else Right(name) } [/scala] Ähnlich wie Option Either kann auf verschiedene Weise verwendet werden. Es unterscheidet sich von Option dadurch, dass Sie immer die so genannte Projektion angeben müssen, mit der Sie über die Methode Left oder Right arbeiten wollen: [scala] // Wenden Sie eine Funktion auf das erfolgreiche Ergebnis an validateName(inputName).right.map(.toUpperCase) // Kombinieren Sie mit anderen Überprüfungen, um beim ersten Fehler einen Kurzschluss zu verursachen. // Rückgabe einer neuen Either[Person] für { Name <- validateName(inputName).right Alter <- validateAge(inputAge).right } yield Person(Name, Alter) // Behandeln Sie sowohl den linken als auch den rechten Fall validateName(inputName).fold { Fehler => s "Validierung fehlgeschlagen: $error", Ergebnis => s "Validierung erfolgreich: $result" } // Und natürlich funktioniert auch der Mustervergleich validateName(inputName) match { case Links(Fehler) => s "Validierung fehlgeschlagen: $error", case Rechts(Ergebnis) => s "Validierung erfolgreich: $result" } // In eine Option umwandeln: validateName(inputName).right.toOption [/scala] Diese Projektion ist etwas ungeschickt und kann in for-Ausdrücken zu verschiedenen verworrenen Compiler-Fehlermeldungen führen. Siehe z.B. die ausgezeichnete und ausführliche Diskussion des Typs Either im The Neophyte's Guide to Scala Part 7. Aufgrund dieser Probleme wurden mehrere alternative Implementierungen für eine Art von Entweder geschaffen. Die bekanntesten sind der Typ / in Scalaz und der Typ Or in Scalactic. Beide vermeiden die Projektionsprobleme des Scala-Either-Typs und bieten gleichzeitig zusätzliche Funktionen zur Aggregation mehrerer Validierungsfehler in einem einzigen Ergebnistyp. Versuchen Sie Try[T] ist ähnlich wie Either. Es hat ebenfalls 2 Fälle, Success[T] für den erfolgreichen Fall und Failure[T] für den Fehlerfall. Der Hauptunterschied besteht darin, dass der Fehler nur vom Typ Throwable sein kann. Sie können ihn anstelle eines try/catch-Blocks verwenden, um die Behandlung von Ausnahmen zu verschieben. Sie können es auch als Scalas Version von geprüften Ausnahmen betrachten. Success[T] umhüllt den Ergebniswert vom Typ T, während der Failure-Fall nur eine Ausnahme enthalten kann. Vergleichen Sie diese 2 Methoden, die einen Integer parsen: [scala] // Wirft eine NumberFormatException, wenn die Ganzzahl nicht geparst werden kann def parseIntException(value: String): Int = value.toInt // Fängt die NumberFormatException ab und gibt einen Failure mit dieser Ausnahme zurück // ODER gibt einen Success mit dem geparsten Integer-Wert zurück def parseInt(value: String): Try[Int] = Try(value.toInt) [/scala] Die erste Funktion benötigt eine Dokumentation, die beschreibt, dass eine Ausnahme ausgelöst werden kann. Die zweite Funktion beschreibt in ihrer Signatur, was zu erwarten ist und verlangt vom Benutzer der Funktion, den Fehlerfall zu berücksichtigen. Try wird in der Regel verwendet, wenn Ausnahmen weitergegeben werden müssen, wenn die Ausnahme nicht benötigt wird, bevorzugen Sie eine der anderen diskutierten Optionen. Try bietet ähnliche Kombinatoren wie Option[T] und Either[L,R]: [scala] // Wenden Sie eine Funktion auf das erfolgreiche Ergebnis an parseInt(input).map( * 2) // Kombinieren Sie mit anderen Überprüfungen, um beim ersten Fehlschlag einen Kurzschluss zu erzeugen. // Rückgabe eines neuen Try[Stats] für { Alter <- parseInt(inputAlter) Höhe <- parseDouble(inputHöhe) } yield Stats(Alter, Größe) // Verwenden Sie einen Standardwert parseAge(inputAge).getOrElse(0) // In eine Option umwandeln parseAge(inputAge).toOption // Und natürlich funktioniert auch der Mustervergleich parseAge(inputAge) match { case Fehlschlag(Ausnahme) => s "Validierung fehlgeschlagen: ${exception.message}", case Erfolg(Ergebnis) => s "Validierung erfolgreich: $result" } [/scala] Beachten Sie, dass Try bei der Arbeit mit Futures nicht benötigt wird! Futures kombinieren asynchrone Verarbeitung mit den Fähigkeiten von Try zur Behandlung von Ausnahmen! Siehe auch Try ist in Futures kostenlos. Ausnahmen Da Scala auf der JVM läuft, basiert die gesamte Fehlerbehandlung auf niedriger Ebene immer noch auf Ausnahmen. In Scala werden Ausnahmen nur selten verwendet, und sie werden in der Regel nur als letzter Ausweg eingesetzt. Üblicher ist es, sie in einen der oben genannten Typen zu konvertieren. Beachten Sie auch, dass im Gegensatz zu Java alle Ausnahmen in Scala ungeprüft sind. Das Auslösen einer Ausnahme wird Ihre funktionale Komposition unterbrechen und wahrscheinlich zu einem unerwarteten Verhalten beim Aufrufer Ihrer Funktion führen. Sie sollte daher als letzter Ausweg reserviert werden, wenn die anderen Optionen keinen Sinn ergeben. Wenn Sie am Ende der Ausnahmen stehen, müssen Sie sie abfangen. In der Scala-Syntax: [scala] try { dangerousCode() } catch { case e: Exception => println("Oops") } finally { cleanup } [/scala] Was in Scala oft falsch gemacht wird, ist, dass alle Throwables gefangen werden, auch die Java-Systemfehler. Sie sollten niemals Errors abfangen, da sie auf einen kritischen Systemfehler wie den OutOfMemoryError hinweisen. Tun Sie dies also niemals: [scala] try { dangerousCode() } catch { case => println("Oops. Auch hier wurde ein OutOfMemoryError aufgefangen!") } [/scala] Aber stattdessen machen Sie Folgendes: [scala] import scala.util.control.NonFatal try { dangerousCode() } catch { case NonFatal() => println("Ooops. Viel besser, nur die nicht fatalen Ausnahmen landen hier.") } [/scala] Um Ausnahmen in die Typen Option oder Either zu konvertieren, können Sie die in scala.util.control.Exception(scaladoc) bereitgestellten Methoden verwenden: [scala] import scala.util.control.Exception. val i = 0 val result: Option[Int] = catching(classOf[ArithmeticException]) opt { 1 / i } val result: Either[Throwable, Int] = catching(classOf[ArithmeticException]) either { 1 / i } [/scala] Denken Sie schließlich daran, dass Sie eine Exception jederzeit in ein Try umwandeln können, wie im vorherigen Abschnitt beschrieben. TDLR;

  • Option[T] verwenden Sie, wenn ein Wert fehlen oder eine Validierung fehlschlagen kann und Sie sich nicht um die genaue Ursache kümmern. Typischerweise in der Datenabfrage und Validierungslogik.
  • Entweder[L,R], ähnlicher Anwendungsfall wie Option, aber wenn Sie einige Informationen über den Fehler angeben müssen.
  • Try[T], verwenden Sie, wenn etwas Außergewöhnliches passieren kann, das Sie in der Funktion nicht behandeln können. Dies schließt im Allgemeinen Validierungslogik und Fehler beim Datenabruf aus, kann aber verwendet werden, um unerwartete Fehler zu melden.
  • Ausnahmen sollten Sie nur als letzten Ausweg verwenden. Verwenden Sie beim Abfangen von Ausnahmen die von Scala bereitgestellten Hilfsmethoden und niemals catch { _ => }, sondern catch { NonFatal(_) => }

Ein letzter Ratschlag: Lesen Sie sich das Scaladoc für alle hier besprochenen Typen durch. Es gibt viele nützliche Kombinatoren, deren Verwendung sich lohnt.

Verfasst von

Remco Beckers

Contact

Let’s discuss how we can support your journey.