Exception Handling: When and Why?

The main languages I use are C++ and Java.

Both languages support exception handling.

I confess, I may not actually understand exception handling, at least, I certainly don’t understand why you would need it.

I want to build on this question here:

Defensive Programming vs Exception Handling?

The debate between defensive/exception handling I find very interesting.

In my experience, I have yet to encounter a situation I thought required exception handling. As I stated, I may simply not understand the concept correctly. That being said, I am at the point where I think, if you end up in the situation where you need exception handling, the code is improperly structured.

Quite a claim, no doubt, as many professional libraries do use exceptions and are surely written by people that outstrip my skill and experience many times over.

My question is: Has anybody encountered a problem where exception handling was the best possible solution?

If so, please explain in detail the problem, and why exception handling was the best solution.

14

The debate between defensive/exception handling I find very interesting.

You seem to be implying these two concepts are at odds with each other. They aren’t. In fact, one common form of defensive programming is checking a function’s arguments and throwing exceptions if they’re invalid.

But at the same time, if that happens I would prefer if the compiler /stops/ the program and says, “You raised an error.” So I can fix it.

One of the benefits of throwing exceptions is that by default they will stop the program.


A more useful comparison would be error codes versus exceptions. These are both mechanisms for dealing with unavoidable failures. In other words, failures that are not the result of a programming error, but are something that the programmer simply cannot prevent. Common examples include running out of memory, a certain file not existing or not allowing write access, or a network request timing out.

Before exceptions, these sorts of failures were usually indicated by an error code returned by the function that may unavoidably fail. The problems with this approach are: 1) It’s easy for a programmer to simply never check error codes, leaving a nearly endless list of rare and subtle bugs with completely unpredictable consequences. 2) Since it’s just one integer, you don’t get a lot of information about why the failure happened. 3) In many cases, the place where the potentailly failing function is called is not a place where a failure can reasonably be recovered from. This requires the error code to be “passed back up” the call stack by every single function that may call it directly or indirectly. Needless to say, this is error prone.

Exceptions were intended as a solution to these problems. When an exception is thrown, it’s impossible for the programmer to accidentally ignore it; either they go out of their way to catch and handle it, or the program crashes. Exceptions also tend to contain far more information about the failure than a single integer. And you don’t have to write any code to allow exceptions to propagate through a function; they just keep going up until they reach code that actually wants to deal with them.

So the best places to use exceptions are when you have an unavoidable potential failure that would be dangerous to ignore and is typically difficult or impossible to recover from at the place it happens.

As a simple and perhaps somewhat extreme example of that, consider what happens when you run out of memory. Any function that creates objects can potentially run out of memory. If you were to try and handle these errors properly with error codes, almost every single non-trivial function in your program would have to remember to check if allocation failed, check if any of the other functions its calling failed, and return the out of memory code when they do. And there’s no sane way to handle the failure aside from showing an “Out of memory” error message to the user (which is typically only feasible near the top of your program). So the fact that C++ handles this via an exception saves you a tremendous amount of extremely tedious and error-prone code; you only need write the code to show that “Out of memory” error message.


Exceptions are also sometimes useful for detecting and quickly failing on logic errors. Say you write a square root function. It only works on positive numbers, but the compiler can’t stop people from passing a negative number into it. You can simply ignore this problem and declare that anyone making this mistake is invoking undefined behavior (see “design by contract”). But defensive programming is often a good thing, and we’d like our API to be a bit more user-friendly than that. You can’t return an error code since the square root function is expected to return a square root. You can log an error message, but that doesn’t stop the program right away so the programmer may not notice it. If you throw an exception, that will immediately stop the program and display a clear error message, so that’s usually a good choice. Many languages also have some kind of “assertion” mechanism that’s also a good solution for this. Assertions behave very similarly to exceptions, except that you can’t catch them, so they’re simpler in some ways.

5

I have a simple view of this. When a function cannot return a value or a method is unable to satisfy it’s post-conditions, then throw an exception. It doesn’t matter if the exception is thrown 1% of the time or 15% of the time or 40% of the time. Write the cleanest code possible and worry about the cost of throwing an exception when you find a performance problem. It’s much easier to write and read:

retrieve data
calculate statistics
write results

than

retrieve data
got it?
   no, return error 1
calculate statistics
have the results?
   no, return error 2
write results
all ok?
   no, return error 3

Most of the time there’s nothing to be done about any of these conditions and they should be caught and logged at the top level.

The worst thing to do is fill your code with catch blocks that do nothing but log and rethrow. Leave the logging for the top level. Yeah, the stack trace will be deeper. No problem, the interesting part is still at the top.

2

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *