scala - insertar - librerias de java jcreator
Scala: ¿la mejor manera de manejar las excepciones de la biblioteca de Java? (2)
Digamos que estoy usando una biblioteca de Java de mi proyecto Scala. Esa biblioteca de Java genera excepciones en todo el lugar, pero no me siento cómodo simplemente dejándolos propagar "en el mundo Scala", ya que no hay forma de estar seguro de qué excepciones puede lanzar un método Scala (excepto documentándolas). Este es el tipo de código que tiendo a escribir:
def doesNotThrowExceptions(parameter: String): Either[Throwable, T] =
catching(classOf[IOException], classOf[NoSuchAlgorithmException]) either {
// Code calling the Java library
// Code generating a value of type T
}
Luego, normalmente, Either.RightProjection.flatMap
para encadenar métodos que devuelven Either[Throwable, ...]
o Either.RightProjection.map
para mezclar métodos que devuelven Either.RightProjection.map
Either[Throwable, ...]
y otros. O simplemente Either.fold
para hacer algo con el valor Throwable
. De alguna manera, sin embargo, esto todavía no se siente del todo bien .
¿Es esta la forma más "idiomática" de lidiar con las excepciones de Java en Scala? ¿No hay una mejor manera?
No estoy seguro de que haya una manera más idiomática de lidiar con las excepciones de Java porque hay al menos cuatro casos diferentes que se lanzarán:
- Algo que ni tú ni el diseñador de la biblioteca realmente esperaban salir mal, salió mal.
- Algo que el diseñador de la biblioteca esperaba que no funcionara, y usted necesita conocer los detalles.
- Algo que esperabas funcionaría, y no necesitas conocer los detalles.
- A veces el método produce un valor y otras no, y lo comunica lanzando una excepción.
Las mejores prácticas pueden diferir para cada uno de estos casos
1. Excepciones verdaderamente excepcionales.
Scala tiene manejo de excepciones con todas las funciones. No hay nada de malo en dejar que una excepción no anticipada se propague sin ser detectada hasta que esté en un nivel en el que pueda hacer algo al respecto. Empaquetar todas las excepciones posibles en una Either
puede perder mucho tiempo. Solo documente lo que sabe que no está manejando y use try / catch a un nivel adecuadamente alto (por ejemplo, un método saveEverything
probablemente debería ir en un bloque try / catch (o envolver su contenido en uno) porque no importa lo que haya salido mal, Si no se pudo guardar todo, es probable que desee intentar salvar la situación, no solo morir.
En particular, es probable que desee manejar el Error
esta manera y solo empaquetar la Exception
, no todos los Throwable
s, en ninguno de los Either
.
2. Excepciones que debes conocer
Este es el caso del que estás hablando, y ya has dado varias buenas sugerencias sobre cómo tratar con ellos. Como ya ha notado, puede usar la catching
para empaquetar las excepciones en Either
. Entonces tu tambien puedes
a. Use la coincidencia de patrones, que le permitirá separar su Either
con más profundidad:
doesNotThrowExceptions("par").right.map(transformData) match {
case Left(ioe: IOException) => /* ... */
case Left(nsae: NoSuchAlgorithmException) => /* ... */
case Right(x) => /* ... */
case Left(e) => throw e // Didn''t expect this one...
}
segundo. Convertir a Option
después de registrar el error:
doesNotThrowExceptions("par").left.map{ e =>
println("You''re not going to like this, but something bad happened:")
println(e)
println("Let''s see if we can still make this work....")
}.right.toOption
do. Si las excepciones son una parte realmente importante de su control de flujo, Either
puede no ser suficiente. En su lugar, es posible que desee definir su propia clase de tipo Either
con algo más que simplemente Left
y Right
. O puedes anidar el lado izquierdo del Either
:
try { Right(/* java code */) }
catch {
case ioe: IOException => Left(Left(ioe))
case nsae: NoSuchAlgorithmException => Left(Right(nsae))
}
re. Utilice la Validation
Scalaz, que es muy similar a Either
pero se adapta un poco más al manejo de excepciones.
3. Excepciones donde solo necesitas saber que algo salió mal, y
4. Excepciones lanzadas para indicar valor sin retorno.
Aunque estas son conceptualmente dos categorías diferentes, las manejas de la misma manera:
catching(classOf[IOException], classOf[NoSuchAlgorithmException]) opt { ... }
para obtener una Option
nuevo. Luego map
, map
, map
, etc.
Siempre prefiero scala.Either
. scalaz.Validation
sobre scala.Either
. Las dos son estructuras algebraicamente idénticas, pero la primera es funcionalmente más rica (tiene métodos más útiles e instancias de clase de tipo).
Consulte este enlace para obtener una excelente presentación de Chris Marshall sobre scalaz.Validation
y otros artículos valiosos de Scalaz.
Para hablar con el código que utiliza el mecanismo clásico de manejo de excepciones, he definido los siguientes métodos de enriquecimiento y una instancia de clase de tipo:
implicit def catchW[A](underlying: Catch[A]) = new CatchW(underlying)
class CatchW[A](underlying: Catch[A]) {
def validation(body: => A): Validation[Throwable, A] = {
underlying.withApply(_.fail)(body.success)
}
def vnel(body: => A): ValidationNEL[Throwable, A] = {
underlying.withApply(_.failNel)(body.success)
}
}
implicit def catchMonoid[A] = new Monoid[Catch[A]] {
val zero = noCatch
def append(x: Catch[A], y: => Catch[A]) = x or y
}