1

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.

Jean-Philippe Pellet
  • 59,296
  • 21
  • 173
  • 234
riri
  • 519
  • 4
  • 15

4 Answers4

6

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.)

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
1

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.

Nikita Volkov
  • 42,792
  • 11
  • 94
  • 169
  • Huh? Exceptions are intended exactly for cases like this! So that the part of the app that communicates with the user can be decoupled from other parts of the system. If `someHelper` is some locally useful function for transforming data that originates with the user, it certainly shouldn't know about how to communicate with the user in order to display an error message. And the communication module probably shouldn't know about the requirements on the data of `someHelper` in order to validate it. – Ben Dec 22 '11 at 04:02
  • Be warned. Creating an exception fills in its stack trace, which can be an expensive operation. If you go this route, pass the exception by name, so it's only created if you use it: `def someMethod(e: => Exception)`. – leedm777 Dec 22 '11 at 04:34
  • @Ben I meant that exceptions were not intended to be used as holding an app state information, and passing exception instances around (not throwing) and surrounding them with logic looks very much like this – Nikita Volkov Dec 22 '11 at 05:49
  • @dave Thanks, Dave! A nice suggestion. Although my point was to show the trivialness of the construct. – Nikita Volkov Dec 22 '11 at 05:51
  • @NikitaVolkov Ah, fair enough then. I didn't read the OP's example as an exception holding app state information, just as an out-of-band communication back to some higher context that can handle the failure. And I thought passing in an exception (or better, an exception factory) was a rather elegant way to allow the calling context to control the kind of exception thrown. – Ben Dec 22 '11 at 06:11
  • 2
    I agree, this is an abuse of the JVM's exceptions capability. Exceptions are a way of handling abnormal events that would otherwise cause the application to crash, circumventing normal control flow. They should absolutely not be used for invalid data, which is part of normal processing. – Luigi Plinge Dec 22 '11 at 18:33
  • @LuigiPlinge You've stated exactly what I wanted to say, but just couldn't find the right words. Thanks! – Nikita Volkov Dec 22 '11 at 23:43
1

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_!.

Community
  • 1
  • 1
leedm777
  • 23,444
  • 10
  • 58
  • 87
0

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 .

Community
  • 1
  • 1
rxg
  • 3,777
  • 22
  • 42