I have handled exception as follows:
def calculate(input: Option[Double]): Try[String] =
Try {
input match {
case (Some(value)) => value.toString
case (None) => throw new IllegalArgumentException("No value found")
}
}
And in client code:
val result = calculate(....)
result match {
case Success(i) => println(i)
case Failure(s) => throw s // or log(s) to log the issue and continue
}
Is it good enough practice or much better can be done for clean and elegant code base?
Try usually used to cover parts which might throw an error, like in cases if you are using some Java libs, which can throw an exception. But, if you would like to return possible error and force client to handle it, Either[A, B] is much better option, at least because you can specify more precise error type for Left[A] and safely to pattern match over your's A type, instead of do possibly incorrect pattern matching against some Throwable, like you would do for Failure(t).
So, in your case possible solution would look like:
sealed trait CalculationError
case class Error1(cause: String) extends CalculationError
def calculate(input: Option[Double]): Either[CalculationError, String] =
input match {
case (Some(value)) => Right(value.toString)
case (None) => Left(Error1("No value found"))
}
}
val result = calculate(....)
result match {
case Right(i) => println(i)
case Left(Error1(s)) => println(s)
}
This is safer approach, because you can later add another type of error , say case class Error2(cause: String) extends CalculationError and on client pattern matching code part, compile will show a warn message that you missed handling of new error: Match is not exhaustive. In case of Failure(t) compile won't be able suggest such warning, so it's easier to make mistake on error handling side.
Hope this helps!
Related
I have a case class QueryParamsas follows:
case class QueryParams(
limit: Option[Integer] = None,
refresh: Option[Boolean] = None,
organisationalUnit: Option[String] = None)
These values limit,refresh,organisationalUnit are actually passed as query parameters in request url for play application.
I need to write a code to check if request URL contains any value for organisationalUnit and if yes I need to throw error .If no, I need to proceed with further operations.
Can anyone help me here
Options are quite good for this kind of thing:
val params: QueryParams = ???
params.organizationalUnit.foreach(_ => throw new Exception("your error message"))
In this way you'll throw only if organizationalUnit is defined. You can also express it as follows:
for (_ <- params.organizationalUnit) {
throw new Exception("your error message")
}
Or alternatively:
if (params.organizationalUnit.isDefined) {
throw new Exception("your error message")
}
The latter is probably the most readable, even though it may not be recognized as very idiomatic according to certain coding styles.
The answer from stefanobaghino is good but I prefer pattern matching for such cases:
params.organisationalUnit match {
case Some(_) => // processing
case _ => //logging
}
If you need other values you can match the whole instance
params match {
case QueryParams(Some(limit), Some(refresh), Some(organisationalUnit)) =>
case QueryParams(mayBeLimit, mayBeRefresh, Some(organisationalUnit)) =>
case _ =>
}
I have something like this:
def mySideEffectingMethod(): Unit = {
Try(someSideEffectingJavaMethod()) match {
case Failure(_: SomeExpectedException) => someSideEffectingJavaMethod() //perhaps attempt it again, for example
case Failure(ex) => throw ex
case Success(_) => //unit
}
}
Is there a way in which I could lose the ugly case Success(_) => //unit? I don't think recover and recoverWith will cut it because they will in this case return Try[Unit] instead of Unit thus suppressing the non-expected exceptions.
You use the Try[T] monad when you want to treat exceptions and errors in a functional way. In particular, using the Try[T] monad lets you to accumulate exception and continue to elaborate results that belongs to Success[T], which is a subclass of Try[T].
It seems that in your case you need only to treat exceptional behaviour. In this case you can simply use the try statement.
def mySideEffectingMethod = {
try {
someSideEffectingJavaMethod
} catch {
case _: SomeExpectedException => someSideEffectingJavaMethod()
//perhaps attempt it again, for example
case ex => throw ex
}
}
In you case, you can use the try statement because you're doing some side effects on inputs. But, if you adopt a more functional approach, which means no side effects at all, you have to revamp the Try monad.
I think that one of the best guides that explains how to treat exceptions in a functional way in Scala is The Neophyte's Guide to Scala Part 6: Error Handling With Try.
An alternative using the Try type:
def mySideEffectingMethod(): Unit = {
Try(someSideEffectingJavaMethod()).failed.foreach {
case _: SomeExpectedException =>
//perhaps attempt it again, for example
someSideEffectingJavaMethod()
case ex => throw ex
}
}
Scala's Try is very useful.
I'd like to use that pattern, but log all exceptions.
How can I do this?
Define the following helper:
import scala.util.{Try, Failure}
def LogTry[A](computation: => A): Try[A] = {
Try(computation) recoverWith {
case e: Throwable =>
log(e)
Failure(e)
}
}
Then you can use it as you would use Try, but any exception will be logged through log(e).
Starting Scala 2.13, the chaining operation tap can be used to apply a side effect (in this case some logging) on any value while returning the original value:
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")
Or an equivalent pattern matching version:
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")
The tap chaining operation applies a side effect (in this case println or some logging) on a value (in this case a Try) while returning the original unmodified value on which tap is applied (the Try):
def tap[U](f: (A) => U): A
You can tweak it even further using implicit class
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
}
}
Now you can call someMethod and on its result call log like this:
scala> someMethod(1/0).log
Failure :( java.lang.ArithmeticException: / by zero
and
scala> someMethod(1).log
Success :) 1
Of course println method inside implicit class can be substituted with any logging you want.
You used the term "exceptions" which is ambiguous. (java.lang.)Throwable is the root of anything that can be placed behind the throw term. java.lang.Exception is one of the two descendants of Throwable (the other being java.lang.Error). Further making this ambiguous is java.lang.RuntimeException, a descendant of Exception, which is probably where you mostly want to spend your logging time (unless you are doing lower level application framework or hardware driver implementations).
Assuming you are wanting to log literally ALL instances of Throwable, then you would need something like this (NOT RECOMMENDED):
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
}
The external try/catch is required to capture all the Throwable instances which are filtered away by scala.util.control.NonFatal within the Try's try/catch block.
That said...there is a Java/JVM rule: you should never define a catch clause at the resolution of Throwable (again, unless you are doing lower level application framework or hardware driver implementations).
Following the intention of this rule, you would need to narrow the Throwable to you only emitted logging at the finer grained level, say something more refined, like java.lang.RuntimeException. If so, the code would look like this (recommended):
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
}
In both code snippets above, you will notice that I used match as opposed to .recoverWith. This is to facilitate easily adding a rethrow that works. It turns out that all the methods on Try are themselves also wrapped with try/catch blocks. This means that if you want to log the Throwable and then rethrow it, if you are using one of the Try methods like recoverWith, the rethrow is immediately recaught and placed into a Failure thereby completely undermining the value of the intentional rethrow. By using match, the rethrow is guaranteed to succeed as it remains outside any of the Try methods.
If you would like to see more of the rabbit holes around this particular area, I created a blog post of my own exploration.
In my scala code, I have some nested Try() match {}, which look ugly:
import scala.util._
Try(convertJsonToObject[User]) match {
case Success(userJsonObj) =>
Try(saveToDb(userJsonObj.id)) match {
case Success(user) => Created("User saved")
case _ => InternalServerError("database error")
}
case _ => BadRequest("bad input")
}
Is there any better way of writing such code?
There's a bunch of ways to solve this problem. I'll give you one possibility. Consider this cleaned up version of your code:
trait Result
case class BadRequest(message:String) extends Result
case class InternalServerError(message:String) extends Result
case class Created(message:String) extends Result
def processRequest(json:String):Result = {
val result =
for{
user <- Try(parseJson(json))
savedUser <- Try(saveToDb(user))
} yield Created("saved")
result.recover{
case jp:JsonParsingException => BadRequest(jp.getMessage)
case other => InternalServerError(other.getMessage)
}.get
}
def parseJson(json:String):User = ...
def saveToDb(user:User):User = ...
The caveat to this code is that it assumes that you can differentiate the json parsing failure from the db failure by the exception each might yield. Not a bad assumption to make though. This code is very similar to a java try/catch block that catches different exception types and returns different results based on catching those different types.
One other nice thing about this approach is that you could just define a standard recovery Partial Function for all kinds of possible exceptions and use it throughout your controllers (which I'm assuming this code is) to eliminate duplicate code. Something like this:
object ExceptionHandling{
val StandardRecovery:PartialFunction[Throwable,Result] = {
case jp:JsonParsingException => BadRequest(jp.getMessage)
case sql:SQLException => InternalServerError(sql.getMessage)
case other => InternalServerError(other.getMessage)
}
}
And then in your controller:
import ExceptionHandling._
result.recover(StandardRecovery).get
Another approach is to define implicit reads for User (if using Play Framework) and then doing something like
someData.validate[User].map { user =>
saveToDb(user.id) match { // you can return Try from saveToDb
case Success(savedUser) => Created("User saved")
case Failure(exception) => InternalServerError("Database Error")
}
}.recoverTotal {
e => BadRequest(JsError.toFlatJson(e))
}
Try(convertJsonToObject[User]).map([your code]).toOption.getOrElse(fallback)
Very often i end up with lots of nested .map and .getOrElse when validating several consecutives conditions
for example:
def save() = CORSAction { request =>
request.body.asJson.map { json =>
json.asOpt[Feature].map { feature =>
MaxEntitiyValidator.checkMaxEntitiesFeature(feature).map { rs =>
feature.save.map { feature =>
Ok(toJson(feature.update).toString)
}.getOrElse {
BadRequest(toJson(
Error(status = BAD_REQUEST, message = "Error creating feature entity")
))
}
}.getOrElse {
BadRequest(toJson(
Error(status = BAD_REQUEST, message = "You have already reached the limit of feature.")
))
}
}.getOrElse {
BadRequest(toJson(
Error(status = BAD_REQUEST, message = "Invalid feature entity")
))
}
}.getOrElse {
BadRequest(toJson(
Error(status = BAD_REQUEST, message = "Expecting JSON data")
))
}
}
You get the idea
I just wanted to know if there's some idiomatic way to keep it more clear
If you hadn't had to return a different message for the None case this would be an ideal use-case for for comprehension. In your case , you probably want to use the Validation monad, as the one you can find in Scalaz. Example ( http://scalaz.github.com/scalaz/scalaz-2.9.0-1-6.0/doc.sxr/scalaz/Validation.scala.html ).
In functional programming, you should not throw exceptions but let functions which can fail return an Either[A,B], where by convention A is the type of result in case of failure and B is the type of result in case of success. You can then match against Left(a) or Right(b) to handle, reespectively, the two cases.
You can think of the Validation monad as an extended Either[A,B] where applying subsequent functions to a Validation will either yield a result, or the first failure in the execution chain.
sealed trait Validation[+E, +A] {
import Scalaz._
def map[B](f: A => B): Validation[E, B] = this match {
case Success(a) => Success(f(a))
case Failure(e) => Failure(e)
}
def foreach[U](f: A => U): Unit = this match {
case Success(a) => f(a)
case Failure(e) =>
}
def flatMap[EE >: E, B](f: A => Validation[EE, B]): Validation[EE, B] = this match {
case Success(a) => f(a)
case Failure(e) => Failure(e)
}
def either : Either[E, A] = this match {
case Success(a) => Right(a)
case Failure(e) => Left(e)
}
def isSuccess : Boolean = this match {
case Success(_) => true
case Failure(_) => false
}
def isFailure : Boolean = !isSuccess
def toOption : Option[A] = this match {
case Success(a) => Some(a)
case Failure(_) => None
}
}
final case class Success[E, A](a: A) extends Validation[E, A]
final case class Failure[E, A](e: E) extends Validation[E, A]
Your code now can be refactored by using the Validation monad into three validation layers. You should basically replace your map with a validation like the following:
def jsonValidation(request:Request):Validation[BadRequest,String] = request.asJson match {
case None => Failure(BadRequest(toJson(
Error(status = BAD_REQUEST, message = "Expecting JSON data")
)
case Some(data) => Success(data)
}
def featureValidation(validatedJson:Validation[BadRequest,String]): Validation[BadRequest,Feature] = {
validatedJson.flatMap {
json=> json.asOpt[Feature] match {
case Some(feature)=> Success(feature)
case None => Failure( BadRequest(toJson(
Error(status = BAD_REQUEST, message = "Invalid feature entity")
)))
}
}
}
And then you chain them like the following featureValidation(jsonValidation(request))
This is a classic example of where using a monad can clean up your code. For example you could use Lift's Box, which is not tied to Lift in any way. Then your code would look something like this:
requestBox.flatMap(asJSON).flatMap(asFeature).flatMap(doSomethingWithFeature)
where asJson is a Function from a request to a Box[JSON] and asFeature is a function from a Feature to some other Box. The box can contain either a value, in which case flatMap calls the function with that value, or it can be an instance of Failure and in that case flatMap does not call the function passed to it.
If you had posted some example code that compiles, I could have posted an answer that compiles.
I tried this to see if pattern matching offered someway to adapt the submitted code sample (in style, if not literally) to something more coherent.
object MyClass {
case class Result(val datum: String)
case class Ok(val _datum: String) extends Result(_datum)
case class BadRequest(_datum: String) extends Result(_datum)
case class A {}
case class B(val a: Option[A])
case class C(val b: Option[B])
case class D(val c: Option[C])
def matcher(op: Option[D]) = {
(op,
op.getOrElse(D(None)).c,
op.getOrElse(D(None)).c.getOrElse(C(None)).b,
op.getOrElse(D(None)).c.getOrElse(C(None)).b.getOrElse(B(None)).a
) match {
case (Some(d), Some(c), Some(b), Some(a)) => Ok("Woo Hoo!")
case (Some(d), Some(c), Some(b), None) => BadRequest("Missing A")
case (Some(d), Some(c), None, None) => BadRequest("Missing B")
case (Some(d), None, None, None) => BadRequest("Missing C")
case (None, None, None, None) => BadRequest("Missing D")
case _ => BadRequest("Egads")
}
}
}
Clearly there are ways to write this more optimally; this is left as an exercise for the reader.
I agree with Edmondo suggestion of using for comprehension but not with the part about using a validation library (At least not anymore given the new features added to scala standard lib since 2012). From my experience with scala, dev that struggle to come up with nice statement with the standard lib will also end up doing the same of even worst when using libs like cats or scalaz. Maybe not at the same place, but ideally we would solve the issue rather than just moving it.
Here is your code rewritten with for comprehension and either that is part of scala standard lib :
def save() = CORSAction { request =>
// Helper to generate the error
def badRequest(message: String) = Error(status = BAD_REQUEST, message)
//Actual validation
val updateEither = for {
json <- request.body.asJson.toRight(badRequest("Expecting JSON data"))
feature <- json.asOpt[Feature].toRight(badRequest("Invalid feature entity"))
rs <- MaxEntitiyValidator
.checkMaxEntitiesFeature(feature)
.toRight(badRequest("You have already reached the limit"))
} yield toJson(feature.update).toString
// Turn the either into an OK/BadRequest
featureEither match {
case Right(update) => Ok(update)
case Left(error) => BadRequest(toJson(error))
}
}
Explanations
Error handling
I'm not sure how much you know about either but they are pretty similar in behaviour as Validation presented by Edmondo or Try object from the scala library. Main difference between those object regard their capability and behaviour with errors, but beside that they all can be mapped and flat mapped the same way.
You can also see that I use toRight to immediately convert the option into Either instead of doing it at the end. I see that java dev have the reflex to throw exception as far as they physically can, but they mostly do so because the try catch mechanism is unwieldy: in case of success, to get data out of a try block you either need to return them or put them in a variable initialized to null out of the block. But this is not the case is scala: you can map a try or an either, so in general, you get a more legible code if you turn results into error representation as soon as have identified it as they are identified as incorrect.
For comprehension
I also know that dev discovering scala are often quite puzzled by for comprehension. This is quite understandable as in most other language, for is only used for iteration over collections while is scala, it seem to use usable on a lot of unrelated types. In scala for is actually more nicer way to call the function flatMap. The compiler may decide to optimize it with map or foreach but it remain correct assume that you will get a flatMap behavior when you use for.
Calling flatMap on a collection will behave like the for each would in other language, so scala for may be used like a standard for when dealing with collection. But you can also use it on any other type of object that provide an implementation for flatMap with the correct signature. If your OK/BadRequest also implement the flatMap, you may be able to use in directly in the for comprehension instead of usong an intermediate Either representation.
For the people are not at ease with using for on anything that do not look like a collection, here is is how the function would look like if explicitly using flatMap instead of for :
def save() = CORSAction { request =>
def badRequest(message: String) = Error(status = BAD_REQUEST, message)
val updateEither = request.body.asJson.toRight(badRequest("Expecting JSON data"))
.flatMap { json =>
json
.asOpt[Feature]
.toRight(badRequest("Invalid feature entity"))
}
.flatMap { feature =>
MaxEntitiyValidator
.checkMaxEntitiesFeature(feature)
.map(_ => feature)
.toRight(badRequest("You have already reached the limit"))
}
.map { rs =>
toJson(feature.update).toString
}
featureEither match {
case Right(update) => Ok(update)
case Left(error) => BadRequest(toJson(error))
}
}
Note that in term of parameter scope, for behave live if the function where nested, not chained.
Conclusion
I think that more than not using the right framework or the right language feature, the main issue with the code your provided is how errors are dealt with. In general, you should not write error paths as after thought that you pile up at the end of the method. If you can deal with the error immediately as they occur, that allow you to move to something else. On the contrary, the more you push them back, the more you will have code with inextricable nesting. They are actually a materialization of all the pending error cases that scala expect you to deal with at some point.