21

I've always wondered how many different exception classes I should implement and throw for various pieces of my software. My particular development is usually C++/C#/Java related, but I believe this is a question for all languages.

I want to understand what is a good number of different exceptions to throw, and what the developer community expect of a good library.

The trade-offs I see include:

  • More exception classes can allow very fine grain levels of error handling for API users (prone to user configuration or data errors, or files not being found)
  • More exception classes allows error specific information to be embedded in the exception, rather than just a string message or error code
  • More exception classes can mean more code maintenance
  • More exception classes can mean the API is less approachable to users

The scenarios I wish to understand exception usage in include:

  • During 'configuration' stage, which might include loading files or setting parameters
  • During an 'operation' type phase where the library might be running tasks and doing some work, perhaps in another thread

Other patterns of error reporting without using exceptions, or less exceptions (as a comparison) might include:

  • Less exceptions, but embedding an error code that can be used as a lookup
  • Returning error codes and flags directly from functions (sometimes not possible from threads)
  • Implemented an event or callback system upon error (avoids stack unwinding)

As developers, what do you prefer to see?

If there are MANY exceptions, do you bother error handling them separately anyway?

Do you have a preference for error handling types depending on the stage of operation?

3
  • 1
    perhaps related: programmers.stackexchange.com/questions/3713/…
    – Fuzz
    Nov 6, 2011 at 13:50
  • 5
    "the API is less approachable to users" - can be dealt with by having several exception classes which all inherit from a common base class for the API. Then just as you're getting started, you can see which API the exception has come from without having to worry about all the details. By the time you're completing your first program using the API, then if you need more information about the error then you start looking at what the different exceptions actually mean. Of course you simultaneously have to play nicely with the standard exception hierarchy of the language you're using. Nov 6, 2011 at 14:36
  • pertinent point Steve
    – Fuzz
    Nov 6, 2011 at 14:43

2 Answers 2

18

I keep it simple.

A library has a base exception type extended from std:::runtime_error (that's from C++ apply as appropriate to other languages). This exception takes a message string so we can log; every throw point has a unique message (usually with a unique ID).

That's about it.

Note 1: In the situations where somebody catching the exception can fix the exceptions and re-start the action. I will add derived exceptions for things that can be potentially uniquely be fixed at a remote location. But this is very very rare (Remember the catcher is unlikely to be close to the throw point thus fixing the problem is going to be hard (but everything is dependent on situation)).

Note 2: Sometimes the library is so simple it is not worth giving it its own exception and std::runtime_error will do. It is only important to have an exception if the ability to distinguish it from std::runtime_error can give the user enough information to do something with it.

Note 3: Within a class I usually prefer error codes (but these will never escape across the public API of my class).

Looking at your trade offs:

The trade-offs I see include:

More exception classes can allow very fine grain levels of error handling for API users (prone to user configuration or data errors, or files not being found)

Do more exceptions really give you finer grain control? The question becomes can the catching code really fix the error based on the exception. I am sure there are situations like that and in these cases you should have another exception. But all the exceptions you have listed above the only useful correction is to generate a big warning and stop the application.

More exception classes allows error specific information to be embedded in the exception, rather than just a string message or error code

This is great reason for using exceptions. But the information must be useful to the person who is caching it. Can they use the information to perform some corrective action? If the object is internal to your library and can not be used to influence any of the API then the information is useless. You need to be very specific that the information thrown has a useful value to the person that can catch it. The person catching it is usually outside your public API so tailor your information so that it can be used with things in your public API.

If all they can do is log the exception then it is best to just throw an error message rather than lots of data. As the catcher will usually build an error message with the data. If you build the error message then it will be consistent across all catchers, if you allow the catcher to build the error message you could get the same error reported differently depending on who is calling and catching.

Less exceptions, but embedding an error code that can be used as a lookup

You have to determine weather the error code can be used meaningfully. If it can then you should have its own exception. Otherwise your users now need to implement switch statements inside there catch (which defeats the whole point of having catch automatically handle stuff).

If it can't then why not use an error message in the exception (no need to split the code and the message it makes it a pain to look up).

Returning error codes and flags directly from functions (sometimes not possible from threads)

Returning error codes is great internally. It allows you to fix bugs there and then and you have to make sure you fix all error codes and account for them. But leaking them across your public API is a bad idea. The problem is that programmers often forget to check for error states (at least with an exception an unchecked error will force the application to quit an un-handled error will generally corrupt all your data).

Implemented an event or callback system upon error (avoids stack unwinding)

This method is often used in conjunction with other error handling mechanism (not as an alternative). Think of your windows program. A user initiates an action by selecting a menu item. This generates an action on the event queue. The event queue eventually assigns a thread to handle the action. The thread is supposed to handle the action and eventually return to the thread pool and await another task. Here an exception must be caught at the base by the thread tasked with the job. The result of catching the exception will usually result in an event being generated for the main loop which will eventually result in an error message being displayed to the user.

But unless you can continue in the face of the exception the stack is going to unwind (for the thread at least).

1
  • +1; Tho "Otherwise your users now need to implement switch statements inside there catch (which defeats the whole point of having catch automatically handle stuff)." - Call stack unwinding (if you are still able to use it) and forced error handling are still great benefits. But it certainly will detract from the ability to do call stack unwinding when you have to catch it in the middle and rethrow. Nov 6, 2011 at 18:51
9

I usually start with:

  1. An exception class for argument bugs. E.g. "argument not allowed to be null", "argument must be positive", and so on. Java and C# have predefined classes for those; in C++ I usually create just one class, derived from std::exception.
  2. An exception class for precondition bugs. Those are for more complex tests, like "index must be smaller than the size".
  3. An exception class for assertion bugs. Those are for checking the state for constistency halfway methods. E.g. when iterating over a list to count elements that are negative, zero, or positive, at the end those three should add up to the size.
  4. One base exception class for the library itself. At first, just throw this class. Only when the need arise, start adding sub-classes.
  5. I prefer not to wrap exceptions, but I know that opinions differ on this point (and is very correlated to the language used). If you do wrap, you'll need additional wrapping exception classes.

As the classes for the first 3 cases are debugging aids, they are not intended to be handled by the code. Instead, they should be caught only by a top-level handler that displays the info such that the user can copy-paste it to the developer (or even better: press the "send report" button). So include info that is useful for the developer: file, function, line-number, and some message that clearly identifies which check failed.

As the first 3 cases are the same for each project, in C++ I usually just copy them from the previous project. Because many are doing exactly the same, the designers of C# and Java added standard classes for those cases to the standard library. [UPDATE:] For the lazy programmers: one class could be enough and with some luck your standard library already has a suitable exception class. I prefer to add info like filename and linenumber, which the default classes in C++ don't provide. [End update]

Depending on the library, the fourth case could have one class only, or could become a handfull of classes. I prefer the agile aproach to start simple, adding sub-classes when the need arise.

For a detailed argumentation about my fourth case, see Loki Astari's answer. I totally agree with his detailed answer.

1
  • +1; There is a definite difference between how you should write write exceptions in a framework verses how you should write exceptions in an application. The distinction is a little fuzzy, but you do mention it (deferring to Loki for the library case). Nov 6, 2011 at 18:54

Your Answer

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

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