6

In my scala code (libraries as well as applications) I currently use a mixture of Option and Try, whenever either of both feels more apropriate.

I tend to implement "doSomething"-methods which can succeed with a return value or with a failure with Try. That is, they can contain code which throw, or, when I detect errors "by hand", I artificially create a Throwable and return a Failure. The return value of those methods hence is a Try[ReturnType].

Now I read that creating exceptions is somewhat suboptimal, because it creates a stack trace (and hence is slow), which I don't even need. I also saw examples using a subclass of ControlThrowable, which don't create a stack trace, however they also don't have a message, and Try of course won't catch it.

Now my concrete question would be, should I generally favour Either over Try when I want to do runtime error-handling / method return values, and use Try only in situations where I actually need to catch something (e.g. thirdparty code)?
That way I would not have to create clumsy Throwables, but instead only use e.g. strings in Left for errors.

So basically:

  • Option: every day usage for something which plain has a value or not
  • Try: catching exceptions within methods, but not used as return value
  • Either: universal return value, containing error (string) or success value

Would this concept work out well, or is there a more viable/common approach?

user826955
  • 3,137
  • 2
  • 30
  • 71
  • 2
    You can pretty easily define a subclass of `Throwable` that doesn't create a stack trace via the constructor with `writableStackTrace`. – Travis Brown Feb 13 '19 at 08:10
  • 1
    there is also the `NoStackTrace` trait in Scala – Thilo Feb 13 '19 at 08:13
  • 3
    Not really an answer, but my own view is that the two relevant choices are `Try` and `Either[Throwable, ?]`, and the decision is largely cultural: choose `Either` if you're using Cats and Cats-associated libraries, consider `Try` otherwise, and be consistent, converting when necessary as early as possible. – Travis Brown Feb 13 '19 at 08:13
  • Whatever you do, stay away from `ControlThrowable`. That is intended for internal use only, when the language designers need to mess with control flow in ways that are otherwise not possible. – Thilo Feb 13 '19 at 08:17
  • There is no such thing as one-ring-to-rule-them-all. Use `Try` (or your own product ADT) where you want a return of Product types and use `Either` if you need a Sum type return. – sarveshseri Feb 13 '19 at 08:22
  • 1
    Well, the core difference would be that the error-part in `Try` is **always** `Throwable`, but in `Either` it can be anything, and as I pointed out, creating a `Throwable` is what I'd like to avoid. Or in other words: *should* I still create a `Throwable` for transporting error-descriptions in the return value, while the same can be achieved with `Either[String,_]` + right? – user826955 Feb 13 '19 at 08:42
  • 1
    @Travis Brown: What would be the benefit of using `Either[Throwable, ?]` over `Try`? – user826955 Feb 13 '19 at 08:45
  • @user826955 1) You're working with libraries that use `Either` (e.g. circe), it's easier to interop with non-`Throwable` failure representations, less magic around where exceptions get caught, etc. – Travis Brown Feb 13 '19 at 09:45
  • @TravisBrown Why do you want to return Either[Throwable, ?] when your error has not been generated because of a caught Throwable and is enough with a String that describe the error or and, for example, a case class with metadata about the context of the error? He is talking about create an Exception to represent an error. – angelcervera Jan 31 '20 at 08:57
  • Remainder: Scala Throwable is an alias to java.lang.Throwable I always try to avoid native java stuff. – angelcervera Jan 31 '20 at 09:03

1 Answers1

4

As Travis Brown has stated in the comments, there is no real convention and it is largely a cultural thing. Consistency is the most important thing here in order to keep your code readable. I've seen codebases which:

  • Use Options to mean "None is a success and any Some(...) contains the error message"
  • Use Try in order to return either a Success containing an empty String or a Failure containing the error message
  • Use Either the wrong way around (ie Either[?, Throwable])

Obviously these aren't good practices, but as long as you're consistent it doesn't really matter. The convention I personally use both recreationally and at work is:

  • Option for when it's valid for a value to be missing (eg when parsing JSON using playframework).
  • Try when attempting to do something that might fail (unless it's in a Future, in which case I just use .recover), where I care about matching the type of exception but also want the result of the Success where available.
  • Either when I don't want to return an Exception/Throwable in my "fail" case (though to be honest this is rare).

Regardless of which of the above I use, I personally think it's best keep it wrapped up for as long as possible. This stops unnecessary Throwables being thrown (haha) around in your code, and makes debugging easier as there aren't any hidden getOrElse lines returning default values (this can get very annoying in large codebases).

James Whiteley
  • 3,363
  • 1
  • 19
  • 46
  • 2
    Thanks for your comment. I decided to go as I suggested in my question. Using `Try` for catching exception from thirdparties, and `Either[A, B]` as general return value, with `A` beeing a custom error type with supplementary info. `Option`, as "usual", is used whenever the absence of a value is allowed. And in general I avoid to use exceptions at all, so never throwing or creating them. – user826955 Feb 21 '19 at 13:01