5

A long time ago I was in a class and the professor had us write individual exception handlers for every possible error.

This seems almost impossible to do when developing large pieces of software given the breadth of exceptions that can occur.

What defines when you should write a custom exception handler vs logging and re-throwing it until it reaches the user?

5
  • 1
    @DocBrown custom exception handler is a handler that does action specific to the error. In other words, most logging is not a custom handler.
    – Basilevs
    Commented Jun 6 at 9:46
  • @Basilevs: after reading the question 3 times (and making some assumptions of the language environment the OP may use), I guess I understand what the OP had in mind. Still I think the question should be improved by adding some example, pseudo code and context.
    – Doc Brown
    Commented Jun 6 at 10:34
  • I think sometimes custom exceptions are used so they can extend exception and provide additional information beyond a standard exception.
    – Jon Raynor
    Commented Jun 6 at 15:40
  • The main differentiator between exceptions and simply using sum types for return values is that you don't need to immediately handle an exception.
    – chepner
    Commented Jun 7 at 14:07
  • Asking students to write a handler for every possible exception type may be motivated in part to get students to realize just how many ways code can go wrong. Commented Jun 8 at 6:12

6 Answers 6

14

... write individual exception handlers for every possible error.

You don't mention which language you were using, but I would guess it was Java.
Java's Checked Exceptions require[d] this.

... when you should write a custom exception handler ...

The same time you should write any exception handler - when you can do something useful with that Exception.

The most important part of Structured Exception Handling is the handling bit.
(Throwing doesn't even get a mention!)

"Handling" means "doing something useful with/about the exception".
The very best exception handler catches an Exception and takes some corrective action that allows the rest of the program to carry on as if nothing had happened.

Catching, [logging] and re-throwing the [same] Exception is Bad.
Throwing exceptions is expensive (i.e. slow). First the runtime has to scan the execution stack to find a suitable handler, collecting any "finally" blocks along the way and then it has to unwind the execution stack, executing those finallys until it "reaches" the handler and carries on from there.
Doing that over and over again (if you have catch's everywhere) is costly. You don't need catches in every function.

Putting a handler where it can do something useful is Good.
The code tries something and it fails, throwing an exception. The handler catches it and tries something else, that works. The rest of the code "above" just carries on.

Catching, [logging] and throwing a new, more general, exception at, say, a Process or Library boundary is Good.
You don't want the caller of your library to know all the details of your implementation (e.g. stack trace), but they still need to know something went wrong. In that case, create and throw a more "generic" exception.
If they might be able to take corrective action, you might consider creating a custom Exception and throwing that (from the boundary layer).

13
  • 17
    "exception throwing is destroying performance" - That depends on the context in which they're used, and also varies strongly from one language to another. I don't think such sweeping generalizations are very helpful.
    – marcelm
    Commented Jun 6 at 13:47
  • 3
    @marcelm Definitely true. While exceptions are known to be very expensive (compared to other control flows) in the C++/Java/C# class of languages, Python's exception handling is sufficiently fast to be used in the generator syntax (StopIteration), and that is so fast that it shows up in the basic underlying flow of asynchronous coroutines.
    – Cort Ammon
    Commented Jun 6 at 14:45
  • 9
    Throwing exceptions is expensive This is an urban legend as far as the JVM is concerned. Creating exceptions with stack traces is expensive, but the unwinding operation is not. Commented Jun 6 at 18:13
  • 3
    @chrylis-cautiouslyoptimistic- IIRC, exception handling was a lot more expensive in the very early days of Java (so the urban legend probably has a grain of truth) — but then it got optimised.  And since exceptions aren't thrown very often (you're not using them for normal control flow, I hope!), it's not usually worth worrying about them; it's almost always more important to think about what makes sense for your program (what makes the code easier to understand and easier to maintain), and not think about low-level performance unless it becomes a problem.
    – gidds
    Commented Jun 6 at 20:13
  • 1
    @Ccm Even in C++ of all languages, the current C++ Core Guidelines (FWIW) recommend the use of exceptions, even saying "We don't consider 'performance' a valid reason not to use exceptions." (source: github.com/isocpp/CppCoreGuidelines/blob/master/…) Commented Jun 6 at 23:38
8

You write your own exception handlers when you want or need to do something in case the exception happens beyond crashing the application.

This might include things like

  • logging information
  • take corrective action
  • gracefully shut down
  • ignore the exception (rare, but in some cases it may be appropriate)

First is clear, logging is important (or can be) to figure out what went wrong.

Corrective action may include retrying. Think a network connection isn't very stable. When it goes down you get an exception. Wait a few seconds and trying again may be all that's needed.

Gracefully shutting down could include giving a nice error message and taking the user back to an earlier step in whatever they were doing. Or closing database connections, file handles, and network connections and terminating the application entirely.

Ignoring, well it's obvious. Not usually advisable, only use where absolutely appropriate (and if it is you'll know about it).

1

A common situation with lots of possible exceptions is downloading any data from the server. You’d write a function that starts the download, you don’t wait for the result because it can take time (usually up to 60 seconds), and the function either calls you back with success, or calls you back with an error. The implementation of this function may catch any number of exceptions.

So first you make sure that this function downloads data without processing it whatsoever, so you can use it for every download situation.

And then you add logic in that one single function for exception handling. It can detect that there was no internet connection and ask the user to turn Wi-Fi on and then retry when WiFi becomes available. Or same situation, but another call has already asked the user to turn on WiFi. So you do nothing but wait for Wi-Fi.

You check for exceptions for debugging purposes. If there is an exception that you have never seen, you want to debug to figure out what’s going on. Then you classify these exceptions: Group 1 = “it doesn’t work and I understand why and it will never work”. Group 2 = “Heaven knows where that comes from, all I know is it doesn’t work”. Group 3 = “A problem that I can fix”, so you just try it again and fix it. Group 4 = “A problem that the user can fix, possibly by waiting”. And if you decide you have a final result, you may show a UI if appropriate and return an error.

I always have two special cases: Background calls that should not show a UI. Like prefetching data. There’s a special case where you could proceed with user interaction, but you are not allowed to. In that case, you show no ui and return an error “no user interface allowed”.

The other is the user cancelling an operation. You show no UI, just return an error “user cancelled”.

So you handle all exceptions, but only in one place.

1
  • 1
    Instructions unclear. How do we find the situations where Group 3 and Group 4 exceptions happen? And how do we distinguish them from errors that have to be logged without specific actions?
    – Basilevs
    Commented Jun 6 at 10:56
1

By your own admission, this was some time ago but I'm wondering if the intention was to make it clear that you should handle any expected exceptions. That is - given the code in the try/catch (or whatever) what sort of exception can be thrown there e.g. ArgumentException, InvalidOperationException. Even if you don't know you can often find out by using the IDE or checking the documentation.

The inference here is that you should rarely (if ever) just handle base exceptions. You may wish to have a catch all once other exceptions have been handled, but this shouldn't be your first port of call.

You can of course write your own custom exception too but there is usually an exception of some type that applies in your case. That being said, throwing an exception is a relatively expensive operation, so you may want to consider one or more other options depending on the circumstance:

  • Log it
  • Return some kind of flag or value
  • Swallow it and continue if that is possible
-1

The primary considerations are:

  1. Can the custom handler do something useful and safe?
  2. What are the consequences of not handling the exception?

It would depend on how exceptions are used.

Exceptional Exceptions

In C++, for example, exceptions are often reserved for "exceptional" errors which leave the program in a bad or unknown state such that continuing could be dangerous. Any error that could be safely handled locally or by the immediate caller would likely be reported in a different way, like returning an error code. Occasionally, C++ exceptions indicate a rare error that need not be catastrophic if properly handled, but that handler probably would have to be far up the call stack.

In these cases, I would reserve exception handling for safety critical programs that might need to shut off the heaters, close the fuel valves, and activate the alarms before crashing. But, even in that case, we're not talking about a custom exception handler for a particular class of exceptions. That's just a catch-all handler similar to one that logs the exception before aborting the execution.

A reason to handle specific exceptions in C++ would be to insulate the program from a different exception style employed by a third-party library. At the layer that calls into the library, the program might catch exceptions the library is known to throw to either handle them, or to propagate the error report using the conventional style.

Expected Exceptions

In languages like Python and Java, exceptions are sometimes (often?) extensions of an API:

Frobnicate(foo) either frobnicates the foo or it throws an exception.

In these cases, the exception is simply a means of reporting that the operation failed. It doesn't mean the program (or runtime) is in a bad state. It's expected that a caller not too far up the call chain could know what to do.

Vectored Exceptions

Handlers for vectored exceptions are akin to interrupt handlers. Instead of unwinding frames to find a handler, a registered handler is called, and it decides how the control flow should proceed.

Consider an implementation of a large-but-sparse array that reserves a huge block of virtual address space but postpones actually committing memory. When the application attempts to access an element, a page fault exception occurs. A custom handler could detect attempts to read or write in the array's reserved range and commit pages on demand. The code accessing the array needn't even be aware of the possibility of a problem, since the handler would return execution to the instruction that originally triggered the fault which would now succeed. (Example in Advanced Windows by Jeffrey Richter.)

If the hardware supports signals for division by zero or arithmetic overflow or underflow, a custom handler for those exceptions could help detect errors in live code, though it probably shouldn't try to fix-up and resume.

0
-2

Unhandled exceptions (or exceptions that bubble up until they get handled by a runtime-provided or framework-provided default handler that logs a stack trace and then aborts a request or whatever) should be extremely rare. They should indicate an OS bug, or an unforeseeable hardware failure, or the CPU getting hit by an ultra-high-energy cosmic ray. If they happen for any other reason, it kind of means that you screwed up.

Exceptions were very popular in the 1990s and early 2000s, but some languages that are gaining in popularity at the moment don't have exceptions per se. Instead, they have some way for a function that might fail to either return a meaningful value, or an error. You have to check whether the function returned an error, and do something with it. And the thing is, you're pretty much obligated to do that right where you called the function. Even if you choose to return that error to your caller for them to deal with, or to add it to a list of errors to deal with later, that choice is made explicitly and enshrined in the code. You can't just add it to a throws declaration or put catch (Exception e) after everything. Which means that you have to constantly be thinking about "well, if this method fails to do the thing I hoped it would, how do I handle that? What does the app do if the network connection drops during this download? How do I treat invalid input? What do I actually want to happen if reading my child's memory usage from cgroupfs returns 'out of memory'?"

Now, I can tell from your question that answering all of those questions, and writing code to implement the answers, sounds really exhausting. And it is a ton of work. In fact it's a very large chunk of the work of a practicing programmer. But it's the work that, more than anything else, separates the good programmers from the not-so-good ones — so you might as well get used to it. If it's possible to get in the habit in a language without exceptions, you can also do it in a language with them.

3
  • 1
    Seems like a very opinionated answer, formulated in a way that makes it sound that it's commonly accepted. For almost everything you say must not be done, or must be done your way, there are very good reasons for the opposite, in significantly many cases. Software development is rarely so black&white as proposed here.
    – AnoE
    Commented Jun 7 at 8:54
  • Handling exception where it is thrown is very counterintuitive. Should not it be handled where the context is rich enough?
    – Basilevs
    Commented Jun 7 at 11:02
  • @Basilevs something always happens right where the exception is thrown. Some languages give you an invisible default of "stop execution, unwind the stack until we find a declared handler, and resume there". But having a ton of invisible control flow isn't good for software quality. If your choice of how to deal with an error is "propagate it out to someplace else that can better deal with it", you can afford the one line of code it takes to document that choice.
    – hobbs
    Commented Jun 7 at 14:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.