override def get(key: String): Option[Any] = {
val timer = couchbaseMetricsTimer.time()
try { // Any way (is it more elegant?) to avoid the try/catch/finally?
Option(couchBaseClient.get(key))
} catch {
case e: Exception => couchbaseFailureMetricCounter.inc(); throw e
} finally {
timer.stop()
}
}
Note the clients of this code are expecting exceptions in case of failures.
2 Answers 2
You could use Try utility.
But the main concern is that it doesn't support finally
statement out-of-the-box.
This can be achieved with some Try
enhancement's value class (by Viktor Klang):
implicit class TryOps[T](val t: Try[T]) extends AnyVal {
def eventually[Ignore](effect: => Ignore): Try[T] = {
val ignoring = (_: Any) => { effect; t }
t transform (ignoring, ignoring)
}
}
Consider this code:
val timer = couchbaseMetricsTimer.time()
val tryClient =
Try(couchBaseClient.get(key)) map { //convert success result
Option(_)
} recover { //process failure result
case e: Exception => couchbaseFailureMetricCounter.inc(); throw e
} eventually { //perform "finally" block
timer.stop()
}
//return actual result
tryClient.get
It's not less, by more idiomatic, so it's up to you which way to choose.
Here are more references to discover.
Note: you can put TryOps
into package object of some of your root package f.e. com.mycompany
, and then you can use it everywhere in your project.
-
2\$\begingroup\$ It's nice that Scala allows to create objects capable of doing what language constructs usually do, but is there any advantage of doing it in this case? The original code is perfectly clear to me (as a Java programmer), the rewritten is sort of clear too, but only because it looks about the same. \$\endgroup\$maaartinus– maaartinus2014年08月29日 18:30:30 +00:00Commented Aug 29, 2014 at 18:30
-
2\$\begingroup\$ In this particular case there seem no difference. But in general,
Try
is monadic construction, which allows you to work with it correspondingly, e.g. avoid unnecessary checks and use chaining calls (f.e. severalmap
s in a row). AlsoTry
can be perfectly useful, when you don't need to have exception thrown immediately in the same thread. By the link I provided in answer more information about features ofTry
. \$\endgroup\$n1ckolas– n1ckolas2014年08月29日 18:45:42 +00:00Commented Aug 29, 2014 at 18:45
An interesting approach to this might be to treat the timer as a resource using the scala-arm library.
You could provide your own implicit implementation of the Resource
type class for the timer so that it would automatically be stopped when it exits the managed scope or if an exception occurs. Ideally you could also increment your analytics counter in the resource implementation. This would completely eliminate the need for the try
/catch
/finally
. This would only work, however, if the couchbaseFailureMetricCounter
can somehow be referenced there. I would have to see the code or scaladocs for the metrics classes to understand whether that would make sense or not.
The concept would go something like this:
import resource._
override def get(key: String): Option[Any] =
managed(couchbaseMetricsTimer.time()) acquireAndGet { _ ⇒
Option(couchBaseClient.get(key))
}
And then, somewhere in the implicit scope, provide the Resource
implementation for the timer type, here assumed to be Timer
. The preferred place to put it would be in the companion object of the Timer
class (if you own the code for that class):
import scala.resource.Resource
object Timer {
implicit object TimerResource extends Resource[Timer] {
def close(timer) = timer.stop()
def closeAfterException(timer, throwable) = {
// not sure where this counter would come from, perhaps
// it could be mixed in or imported
couchbaseFailureMetricCounter.inc()
// super implementation also calls close
super.closeAfterException(timer, throwable)
}
}
}
couchbaseFailureMetricCounter.inc()
to increment failure counter whenever i get an exception. \$\endgroup\$