riri
riri

Reputation: 509

Scala exception as a function argument design pattern

I am writing a web app where exceptions are used to handle error cases. Often, I find myself writing helpers like this:

def someHelper(...) : Boolean {...}

and then using it like this:

if (!someHelper(...)){
    throw new SomeException()
}

These exceptions represent things like invalid parameters, and when handled they send a useful error message to the user, eg

try {
     ...
} catch {
    case e: SomeException => "Bad user!"
}

Is this a reasonable approach? And how could I pass the exception into the helper function and have it thrown there? I have had trouble constructing a type for such a function.

Upvotes: 1

Views: 1234

Answers (4)

rxg
rxg

Reputation: 3982

For an alternative approach you could check out scalaz Validators, which give a lot of flexibility for this kind of case (e.g. should I crash on error, accumulate the errors and report at the end or ignore them completely?). A few examples might help you decide if this is the right approach for you.

If you find it hard to find a way in to the library, this answer gives some pointers to some introductory material; or check out .

Upvotes: 0

Rex Kerr
Rex Kerr

Reputation: 167881

I use Either most of the time, not exceptions. I generally use exceptions, as you have done or some similar way, when the control flow has to go way, way back to some distant point, and otherwise there's nothing sensible to do. However, when the exceptions can be handled fairly locally, I will instead

def myMethod(...): Either[String,ValidatedInputForm] = {
  ...
  if (!someHelper(...)) Left("Agree button not checked")
  else Right(whateverForm)
}

and then when I call this method, I can

myMethod(blah).fold({ err =>
  doSomething(err)
  saneReturnValue
}, { form =>
  foo(form)
  form.usefulField
})

or match on Left(err) vs Right(form), or various other things.

If I don't want to handle the error right there, but instead want to process the return value, I

myMethod(blah).right.map{ form =>
  foo(form)
  bar(form)
}

and I'll get an Either with the error message unchanged as a Left, if it was an error message, or with the result of { foo(form); bar(form) } as a Right if it was okay. You can also chain your error processing using flatMap, e.g. if you wanted to perform an additional check on so-far-correct values and reject some of them, you could

myMethod(blah).right.flatMap{ form =>
  if (!checkSomething(form)) Left("Something didn't check out.")
  else Right(form)
}

It's this sort of processing that makes using Either more convenient (and usually better-performing, if exceptions are common) than exceptions, which is why I use them.

(In fact, in very many cases I don't care why something went wrong, only that it went wrong, in which case I just use an Option.)

Upvotes: 6

leedm777
leedm777

Reputation: 24032

The two most common ways to approach what you're trying to do is to either just have the helper create and throw an exception itself, or exactly what you're doing: have the calling code check the results, and throw a meaningful exception, if needed.

I've never seen a library where you pass in the exception you expect the helper to throw. As I said on another answer, there's a surprisingly substantial cost to simply instantiating an exception, and if you followed this pattern throughout your code you could see an overall performance problem. This could be mitigated through the use of by-name parameters, but if you just forget to put => in a few key functions, you've got a performance problem that's difficult to track down.

At the end of the day, if you want the helper to throw an exception, it makes sense that the helper itself already knows what sort of exception it wants to throw. If I had to choose between A and B:

def helperA(...) { if (stuff) throw new InvalidStuff() }

def helperB(..., onError: => Exception) { if (stuff) throw onError }

I would choose A every time.

Now, if I had to choose between A and what you have now, that's a toss up. It really depends on context, what you're trying to accomplish with the helpers, how else they may be used, etc.

On a final note, naming is very important in these sorts of situations. If your go the return-code-helper route, your helpers should have question names, such as isValid. If you have exception-throwing-helpers, they should have action names, such as validate. Maybe even give it emphasis, like validate_!.

Upvotes: 1

Nikita Volkov
Nikita Volkov

Reputation: 43309

There's nothing special about passing an exception instance to some method:

def someMethod(e: SomeException) {
  throw e
}
someMethod(new SomeException)

But I have to say that I get a very distinct feeling that your whole idea just smells. If you want to validate a user input just write validators, e.g. UserValidator which will have some method like isValid to test a user input and return a boolean, you can implement some messaging there too. Exceptions are really intended for different purposes.

Upvotes: 1

Related Questions