Michael
Michael

Reputation: 42050

How to add call arguments to exceptions in Scala code?

This is a follow-up for my previous question.

Suppose I have functions foo, bar, and qux that raise exceptions.

def foo(x: Int, y: Int, z: Int): String = ...
def bar(s: String): String = ...
def qux(t: String): String = ...

I am composing them as follows:

def myFun(x: Int, y: Int, z: Int): String = {
  val s = foo(x, y, z)
  val t = bar(s)
  qux(t)
}

What if bar raises an exception ? If the exception does not contain s I cannot understand what happened. I probably need to add s to that exception. (The same applies to Either, \/, etc.)

So I am defining an exception, which "captures" the arguments:

case class MyException(e: Exception, args: Any*) extends Exception(e)

and writing myFun as follows:

def myFun(x: Int, y: Int, z: Int): String = {

  def myFoo(x: Int, y: Int, z: Int) = try foo(x, y, z) catch { 
    case NonFatal(e: Exception) => throw MyException(e, x, y, z)
  }

  def myBar(s: String) = try bar(s) catch { 
    case NonFatal(e: Exception) => throw MyException(e, s)
  }

  def myQux(t: String) = try qux(t) catch { 
    case NonFatal(e: Exception) => throw MyException(e, t)
  }

  val s = myFoo(x, y, z)
  val t = myBar(s)
  myQux(t)
}

I have added a lot of code now but I can catch the exception and print it out to see the data I need.

Does it make sense ? Is it possible to get rid of the boilerplate code in myFoo, myBar, myQux ? Does Shapeless help here?

Upvotes: 0

Views: 278

Answers (1)

maasg
maasg

Reputation: 37435

I think you are trying to patch a deeper issue here. In principle, foo, bar, and qux should raise meaningful exceptions. Joshua Bloch in his book "Effective Java" extends on this point where meaningful Exceptions should be constructed with the parameters that help track back the issue at hand.

Imagine this principle applied to your question:

def foo(a:Int, b:Int, c:Int):String = if (a<0) throw new ParameterOutOfBounds("a","lt 0") else s"a->$a,b->$b,c->$c"
def bar(s:String):String = if (s.endsWith("0")) throw IllegalArgumentException(s"Unacceptable suffix ${s.last}") else s
def qux(s:String):String = if (s.endsWith("1")) throw IllegalArgumentException(s"Unacceptable suffix ${s.last}") else s

You can compose these functions and still preserve any Exception on the composition:

def composed(a:Int, b:Int, c:Int):Try[String] = 
for { vfoo <- Try(foo(a,b,c)); 
      vbar <- Try(bar(vfoo)); 
      vqux <- Try(qux(vfoo))}  
yield (vqux)

scala> composed(1,2,3)
res4: scala.util.Try[String] = Success(a->1,b->2,c->3)

scala> composed(-1,2,3)
res7: scala.util.Try[String] = Failure(ParameterOutOfBounds: Invalid Parameter=a.   Reason=lt 0)


scala> composed(1,2,0)
res5: scala.util.Try[String] = Failure(java.lang.IllegalArgumentException: Unacceptable suffix 0)

In case foo,bar and qux are code you cannot modify, I'd wrap those exceptions using the same principle of "something meaningful" instead of packaging their parameters in a generic exception.

Upvotes: 2

Related Questions