scala - tag - Probar con el registro de excepciones
tag questions con i (4)
Al iniciar Scala 2.13
, se puede usar la operación de encadenamiento para aplicar un efecto secundario (en este caso, algunos registros) en cualquier valor mientras se devuelve el valor original:
import util.chaining._
val x = Try("aa".toInt).tap(_.failed.foreach(println))
// java.lang.NumberFormatException: For input string: "aa"
// x: Try[Int] = Failure(java.lang.NumberFormatException: For input string: "aa")
O una versión de coincidencia de patrón equivalente:
val x = Try("aa".toInt).tap { case Failure(e) => println(e) case _ => }
// java.lang.NumberFormatException: For input string: "aa"
// x: Try[Int] = Failure(java.lang.NumberFormatException: For input string: "aa")
La operación de encadenamiento de tap
aplica un efecto secundario (en este caso, println
o algún registro) sobre un valor (en este caso, una Try
) mientras devuelve el valor original sin modificar al que se aplica la tap
(la Try
):
def pulse [U] (f: (A) => U): A
El Try
de Scala es muy útil.
Me gustaría usar ese patrón, pero registrar todas las excepciones.
¿Cómo puedo hacer esto?
Define el siguiente ayudante:
import scala.util.{Try, Failure}
def LogTry[A](computation: => A): Try[A] = {
Try(computation) recoverWith {
case e: Throwable =>
log(e)
Failure(e)
}
}
Luego puede usarlo como lo haría con Try
, pero cualquier excepción se registrará a través del log(e)
.
Puedes modificarlo aún más usando la clase implícita
def someMethod[A](f: => A): Try[A] = Try(f)
implicit class LogTry[A](res: Try[A]) {
def log() = res match {
case Success(s) => println("Success :) " + s); res
case Failure(f) => println("Failure :( " + f); res
}
}
Ahora puede llamar a someMethod
y en su log
llamadas de resultados como este:
scala> someMethod(1/0).log
Failure :( java.lang.ArithmeticException: / by zero
y
scala> someMethod(1).log
Success :) 1
Por supuesto, el método println
dentro de la clase implícita puede sustituirse con cualquier registro que desee.
Usaste el término "excepciones" que es ambiguo. (java.lang.)Throwable
es la raíz de cualquier cosa que pueda colocarse detrás del término de throw
. java.lang.Exception
es uno de los dos descendientes de (java.lang.)Throwable (el otro es java.lang.Error
). Además de hacer esto ambiguo está java.lang.RuntimeException
, un descendiente de Exception
, que es probablemente donde más desea pasar su tiempo de registro (a menos que esté haciendo implementaciones de controlador de hardware o marco de aplicaciones de nivel inferior).
Suponiendo que desea registrar literalmente TODAS las instancias de Throwable, entonces necesitará algo como esto (NO RECOMENDADO):
def logAtThrowable(f: => A): Try[A] =
try
Try(f) match {
case failure @ Failure(throwable) =>
log(s"Failure: {throwable.getMessage}")
failure
case success @ _ =>
//uncomment out the next line if you want to also log Success-es
//log(s"Success: {throwable.getMessage}")
success
}
catch throwable: Throwable => {
//!NonFatal pathway
log(s"Failure: {throwable.getMessage}")
throw throwable
}
El try/catch
externo es necesario para capturar todas las instancias de scala.util.control.NonFatal
que se eliminan por medio de scala.util.control.NonFatal
dentro del bloque try
/ catch
Try
.
Dicho esto ... hay una regla de Java / JVM: nunca debe definir una cláusula catch en la resolución de Throwable (de nuevo, a menos que esté haciendo implementaciones de controlador de hardware o marco de aplicaciones de nivel inferior).
Siguiendo la intención de esta regla, necesitaría restringir el Throwable
para que solo emita el registro en el nivel de grano más fino, diga algo más refinado, como java.lang.RuntimeException
. Si es así, el código se vería así (recomendado):
def logAtRuntimeException(f: => A): Try[A] =
Try(f) match {
case failure @ Failure(throwable) =>
throwable match {
case runtimeException: RuntimeException =>
log(s"Failure: {runtimeException.getMessage}")
}
failure
case success @ _ =>
success
}
En los dos fragmentos de código anteriores, notará que usé match
en lugar de .recoverWith
. Esto es para facilitar fácilmente la adición de un throw
re que funciona. Resulta que todos los métodos en Try
también están envueltos con bloques try
/ catch
. Esto significa que si desea registrar el Throwable
y luego volver a throw
, si está utilizando uno de los métodos de Try
, como recoverWith
, el relanzamiento se recupera inmediatamente y se coloca en una Failure
, minando así completamente el valor del relanzamiento intencional. Mediante el uso de la match
, se garantiza que el relanzará con éxito ya que permanece fuera de cualquiera de los métodos de Try
.
Si desea ver más agujeros de conejos en esta área en particular, creé una fromjavatoscala.blogspot.com/2016/09/… .