33

I think it is accepted that as a general rule in Java (and perhaps any language with exception handling) one should try to avoid using exception handling to actually handle business logic. In general, if it is expected that a certain situation is supposed to happen, one should check for it and handle it more directly than relying on exception handling to do the checking for you. For example, the following is not considered good practice:

try{
  _map.put(myKey, myValue);
} catch(NullPointerException e){
  _map = new HashMap<String, String>();
}

Instead lazy initialization should be accomplished more like this:

if(_map == null){
  _map = new HashMap<String, String>();
}
_map.put(myKey, myValue);

Of course there could be far more complex logic than simply handling lazy initialization. So given that this type of thing is usually frowned upon...when, if ever, is it a good idea to rely on an exception happening for certain business logic to occur? Would it be accurate to say that any instance where one feels compelled to use this approach is really highlighting a weakness of the API being used?

Michael McGowan
  • 6,528
  • 8
  • 42
  • 70
  • 3
    There are exceptions from this "general" rule. In languages with a very poor exceptions implementation (like in Java) it is really not a good idea to use them for anything else than a plain error handling. But in languages like OCaml it is perfectly ok to use exceptions for control flow, as they're really cheap and efficient. – SK-logic Mar 21 '11 at 13:13
  • 4
    This makes me think of Python's iterators. They rely on the `StopException` to cease iteration. A comment on [this answer](http://stackoverflow.com/questions/1966591/hasnext-in-python-iterators/1966609#1966609) alludes to the Python philosophy that "it's easier to ask for forgiveness than permission." – Adam Paynter Mar 21 '11 at 13:16
  • 1
    So it sounds like the philosophy depends more upon the language than I realized...interesting... – Michael McGowan Mar 21 '11 at 13:17

5 Answers5

35

Whenever the exception can be anticipated but not avoided.

Say, if you are relying on an external API of some sort to parse data, and that API offers parse methods but nothing to tell whether a given input can be parsed or not (or if whether the parse can succeed or not depends on factors out of your control, but the API doesn't provide appropriate function calls), and the parsing method throws an exception when the input cannot be parsed.

With a properly designed API, this should boil down to a quantity somewhere in the range "virtually never" to "never".

I can see absolutely no reason to use exception handling as a means of normal flow control in code. It's expensive, it's hard to read (just look at your own first example; I realize it was probably written very quickly, but when _map hasn't been initialized, what you end up with is an empty map, throwing away the entry you were trying to add), and it litters the code with largely useless try-catch blocks, which can very well hide real problems. Again taking your own example, what if the call to _map.add() were to throw a NullPointerException for some reason other than _map being null? Suddenly, you are silently recreating an empty map rather than adding an entry to it. Which I'm sure I don't really have to say can lead to any number of bugs in completely unrelated places in the code because of unexpected state...

Edit: Just to be clear, the above answer is written in the context of Java. Other languages may (and apparently, do) differ in the implementation expense of exceptions, but other points should still hold.

user
  • 6,897
  • 8
  • 43
  • 79
  • +1 for pointing out why the practice is bad using a mistake in the actual example I used (which was hastily put together without much though). – Michael McGowan Mar 21 '11 at 13:24
  • Yes, I figured it was hastily put together, but it's still an important consideration. Your example only highlighted the point. – user Mar 21 '11 at 13:27
  • 3
    Python is an interesting example of an opposing take on this issue; see discussions [here](http://stackoverflow.com/questions/3086806/python-exceptions-eafp-and-what-is-really-exceptional), on [Wikipedia](http://en.wikipedia.org/wiki/Python_syntax_and_semantics#Exceptions), and on [python.net](http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#eafp-vs-lbyl). – eggsyntax Mar 23 '11 at 14:36
  • Except, (1) my answer was written in the context of Java, which the original question's tags indicate was the OP's primary focus; and (2) without having worked in Python, most of the points made should still hold. For example, a high risk of mistakes such as the very ones exemplified by the OP's example. – user Mar 23 '11 at 15:02
12

Throwing an exception is a relatively expensive operation in C++, and an extremely expensive one in Java. On efficiency grounds alone, it never makes sense to avoid an explicit check and accept an exception instead. I suppose you might be able to justify it in some rare cases where checking whether an exception would be thrown is very complex or nearly impossible, but otherwise, I'd say the answer is "pretty much never."

Ernest Friedman-Hill
  • 80,601
  • 10
  • 150
  • 186
  • Correction: instantiation of exception is expensive, throwing is not so. Also, it's not "extremely" expensive. – axtavt Mar 21 '11 at 13:29
  • 2
    I would call fillInStackTrace() extremely expensive relative to a null check. Throwing is not so cheap either; although it doesn't involve calling destructors as in C++, it does involve rooting through internal data structures to find the appropriate handler at each level of the stack. – Ernest Friedman-Hill Mar 21 '11 at 13:35
0

This is really a discussion question, and the answer depends on the design of your system.

My instinct is always a blanket never, but i've seen several systems implement business errors using exceptions. I personally find it disgusting, but I really understand the advantage of breaking a business process as soon as you know that it failed, handling your failure ( e.g. rolling back your Unit Of Work), and returning the error to the caller, perhaps with added information.

one possible disadvantage, is that it's really straightforward to do the error handling across several different classes, so that the definition of what happens when the process fails is really hard to deduce from the code.

Anyway there is no single answer here, you need to weigh both approaches, and sometimes combine them.

regarding your example, I don't see any advantages for using exceptions for flow control, especially in a "good" (designed to work) scenario.

AK_
  • 7,981
  • 7
  • 46
  • 78
-1

There is a reason exceptions are objects. There is also a reason designers of the Java language split all Throwables into 2 main types: checked and unchecked.

is it a good idea to rely on an exception happening for certain business logic to occur?

Yes. Absolutely. You should read Chapter 9 of "Effective Java, Second Edition". It's all there. Nicely explained and waiting for you.

  • 1
    There's a difference between a mere checked exception and what I'm describing. I feel a checked exception is for something unexpected yet there exists a legitimate way to recover from it. What I'm describing is more for when you actually expected the "unexpected" to happen. – Michael McGowan Mar 21 '11 at 13:55
  • @Michael McGowan Sometimes exception is the only way to make something useful. Think of multithreading and blocking calls. –  Mar 21 '11 at 14:03
  • 1
    The purpose of this site is to provide answers, not redirect somebody to another source (as good as that source is). This is no better than saying RTFM. -1. – Mark Peters Mar 21 '11 at 14:34
  • @Mark Peters What's the point of doing something that is already done and in much better way? –  Mar 21 '11 at 15:15
  • 2
    First, not everyone has that book. Second, the goal is for this site to be a *repository* of information. You didn't answer the question, you just told them to look somewhere else for the answer. That's not an answer, but it would be a valid comment. – Mark Peters Mar 21 '11 at 15:21
-3

If you are dealing with an error condition which is actually a part of the business logic, it is OK to use Exceptions. For example:

try{
   // register new user
   if(getUser(name) != null)
      throw new MyAppException("Such user already exists");
   //other registration steps......
}catch(MyAppException ex){
   sendMessage(ex.getMessage());
}
Ma99uS
  • 10,605
  • 8
  • 32
  • 42
  • 4
    Why not just call `sendMessage()` from within the `if` block, avoiding the exception entirely? Throw in a `break`, `continue`, `return` or other appropriate flow-control statement if needed. That also makes it much more clear what's going on. – user Mar 21 '11 at 13:29
  • But you could argue that whoever was calling this function should have performed the test himself. Using checks and exceptions to protect your invariants is good, but that doesn't mean your library's clients shouldn't be doing the same to cover exceptional cases. – Mark Peters Mar 21 '11 at 13:30
  • @Michael: I agree, but a more realistic example would be that the exception gets thrown back to the caller who needs to know that it happened. – Mark Peters Mar 21 '11 at 13:31
  • 2
    I'm sorry, but this is not OK *at all*. Throwing and catching an exception within the same method is very bad style and very inefficient to boot. – Ernest Friedman-Hill Mar 21 '11 at 13:36
  • @Mark: That falls into the category "possible to anticipate but not avoid" in my earlier answer. You can anticipate that another user account with the same name has been created while the user was filling out the registration form, but/and you cannot avoid the possibility until you have actually created the user account yourself. Thus, throwing an exception is appropriate, though doesn't negate the need for checking invariants more gracefully. However, I also agree with @Ernest, throwing and catching within the same method is not good practice at all. – user Mar 21 '11 at 13:49
  • The code that attempts to register the new account should not concern itself with how to convey failure to the human sitting at the computer. One should either throw an exception and let the calling code deal with it, or else have the calling code pass in a ProblemHandler object (with a HandleProblem method which could pop up a dialog box, or do whatever). If an operation will succeed or fail in its entirety, it's best to throw an exception; in cases where a problem might partially succeed (e.g. given a list of accounts to add) it may be best to allow for piecemeal error handling. – supercat Mar 22 '11 at 17:54