Blog
Typsichere Fehlerbehandlung mit Scala 3

Einführung
In einem früheren Blogbeitrag haben wir uns die typsichere Fehlerbehandlung mit Shapeless-Koprodukten angesehen und festgestellt, dass Koprodukte das Fehlen von Union-Typen in Scala 2.x ausgleichen. Ein Koprodukt kann als Either[A, B].
Die Herausforderung bei Shapeless-Koprodukten besteht darin, dass sie zwar perfekt funktionieren, der Code aber nicht so sauber ist, wie er sein könnte, da es keine native Unterstützung für Union-Typen gibt. Das hat sich jedoch in Scala 3 geändert! In diesem Artikel zeige ich Ihnen, wie Sie denselben typisierten Fehlerkanal wie im vorherigen Blogbeitrag erstellen können, aber diesmal mit viel weniger Code und besserer Lesbarkeit!
Warum überhaupt ein getippter Fehlerkanal?
Ein typisierter Fehlerkanal zeigt dem Programmierer explizit an, welche Arten von Fehlern auftreten können, so dass er anhand der Signatur sofort versteht, welche Fälle er behandeln muss. Diese Handler können zu einfachen Funktionen werden, die leicht zu testen sind.
Wenn wir uns die Webentwicklung ansehen, sind mehrere Fehler vorprogrammiert:
- Fehler bei der Eingabevalidierung; sollten an den Benutzer zurückgemeldet werden, damit dieser seine Eingabe korrigieren kann.
- Fehler bei der Domainvalidierung: Je nach Fehler möchten Sie vielleicht nicht den Benutzer selbst benachrichtigen, sondern eine Benachrichtigung für einen Support-Mitarbeiter auslösen, der das Problem beheben soll.
- Unerwartete Ausnahmen; sollten protokolliert werden, eine Warnung auslösen und den Benutzer darüber informieren, dass "etwas schief gelaufen ist". Sie möchten zum Beispiel nicht, dass möglicherweise sensible Daten nach außen dringen.
Code-Beispiel
Lassen Sie uns einen Blick auf den Code werfen:
import scala.io.StdIn.readLine
object DivideCommandLineApp extends App {
case class ParseNumberError(value: String)
case object DivideByZeroError
def tryParse(s: String): Either[ParseNumberError, Double] =
s.toDoubleOption.fold[Either[ParseNumberError, Double]](Left(ParseNumberError(s)))(a => Right(a))
def tryDivide(a: Double, b: Double): Either[DivideByZeroError.type, Double] =
if (b == 0) Left(DivideByZeroError)
else Right(a / b)
def tryRunApp: Either[ParseNumberError | DivideByZeroError.type, Double] = for {
a <- tryParse(readLine)
b <- tryParse(readLine)
r <- tryDivide(a, b)
} yield r
tryRunApp.fold({
case ParseNumberError(error) => println(s"Error: Input '$error' is not a number")
case DivideByZeroError => println("Error: Cannot divide by zero")
}, r => println(s"Result: $r"))
}
Indem wir den Typ Either nutzen, brauchen wir nichts Ausgefalleneres. Da tryRunApp zu sehen ist. Sie gibt entweder ein ParseNumberError oder ein DivideByZeroError zurück.
Voilà! So einfach können Sie in Scala 3 mit Hilfe von Union-Typen Fehlerkanäle einrichten.
Unterschiede
Werfen wir einen kurzen, objektiven Blick auf die Unterschiede zwischen dem Code, der Shapeless-Koprodukte verwendet, und dem unten stehenden Code:
- Rund 70% weniger Code (81 Zeilen gegenüber 24)
- Keine externe Bibliothek erforderlich (d.h. Shapeless)
- Keine Notwendigkeit für Implikate (nicht, dass ich etwas gegen Implikate hätte, aber es kann neue Programmierer in Scala ziemlich verwirren)
Fazit
Die Verwendung von Vereinigungstypen in Scala 3 bietet neue Möglichkeiten für die Modellierung unserer Domänen und unsere Art der Fehlerbehandlung. In Scala 3 ist es nicht mehr nötig, Shapeless-Koprodukte für diese einfachen Union-Typ-Konstruktionen zu verwenden. Bedeutet dies, dass Shapeless obsolet geworden ist? Nein, natürlich nicht! Es gibt immer noch eine Menge Funktionen in Shapeless, die von Scala 3 nicht abgedeckt werden.
Verfasst von

Anton Lijcklama à Nijeholt
Unsere Ideen
Weitere Blogs
Contact



