9

I am currently trying to improve my use of exceptions, and found the important distinction between exceptions that signify programming errors (e.g. someone passed null as argument, or called a method on an object after it was disposed) and those that signify a failure in the operation that is not the caller's fault (e.g. an I/O exception).

How should these two kinds of exceptions be treated differently? Do you think that error exceptions need to be explicitly documented, or is it enough to document the relevant preconditions? And can you leave out the documentation of a precondition or error exception if it should be obvious (for example, ObjectDisposedException when calling a method on a disposed object)

1
  • I usually distinguish another category of exceptions: business exceptions. These refer to error conditions the user is interested in the details of. I typically make those exceptions checked.
    – beluchin
    May 15, 2015 at 11:38

5 Answers 5

2

I think you are on the right track. Neither throwing, catching, nor documenting all potentially throwable exceptions makes much sense. There are times where the product stringency requires a higher degree of exception employment and documentation (e.g. certain safety critical aspects of a systems).

The strategy of being more defensive, using contract concepts to identify preconditions (and postconditions) on especially downstream callers (e.g. anything that resembles a public or protected member) will often be more effective and more flexible. This applies not only to the implementation, but to the documentation. If developers know what is expected, they are more likely to follow the rules and less likely to be confused or misuse the code you have written.

Some of the common things that should be documented include the case of null parameters. Often there is a consequence for their use that drives the result to something that wouldn't normally be expected, but are allowed and used for a variety of reasons, sometimes for flexibility. As a consumer of a member that has parameters that allow null, or other special, non rational values (like negative time, or negative quantities), I expect to see them identified and explained.

For non null parameters, as a consumer of a public or protected member, I want to know that null is not allowed. I want to know what the valid range of values in the given context is. I want to know the consequences of using values that are outside the normal range, but are otherwise valid in a different calling context (eg the value of the type is generally valid for any operation, but not here -- like a boolean parameter that doesn't expect false as a valid value.

As far as platform, or otherwise well known interfaces, I don't think you have to go to extremes in documenting it. However, because you have an opportunity as a developer to vary the implementation from whatever platform guidance, making note of how it follows that guidance may be of value.

Specific to IDisposable, often implementations of this interface offers an alternative method that is preferred over the explicit disposal process. In these cases, highlight the preferred method, and note that the explicit disposal is not preferred.

2
  • I understand the need for documenting which values are allowed, but if you see a function performReadCommand(ICommand cmd, int replySizeBytes) - would you expect that null would be acceptable for cmd, or a negative value for replySizeBytes? IMO documentation would only be necessary if those values actually were allowed, and you should probably address whether 0 is valid for replySizeBytes.
    – Medo42
    Nov 23, 2012 at 9:27
  • They both could be "valid" depending on the implementation. You are or I, and even everyone here would not expect null or negative values to be appropriate for that signature, but they could be. Consider an environment where the reader is a blocking, persistant process, and when the cmd is null could mean to not change the the command used by the reader, and proceed with the next read using the previious command. Ideally in that case a more appropriate method signature that ommitted the cmd as a parameter would be defined and used, but it might not be.
    – JustinC
    Nov 23, 2012 at 15:38
2

Here are my own thoughts. Note that I am not too certain this is the best way to go, which is why I created this question in the first place.

As far as I understand, it makes little sense for an immediate caller to actually handle programming error exceptions, he should instead assure that the preconditions are met. Only "outer" exception handlers at task boundaries should catch them, so they can keep the system running if a task fails.

In order to ensure that client code can cleanly catch "failure" exceptions without catching error exceptions by mistake, I create my own exception classes for all failure exceptions now, and document them in the methods that throw them. I would make them checked exceptions in Java.

Until recently, I tried to document all exceptions that a method could throw, but that sometimes creates an unwiedly list that needs to be documented in every method up the call chain until you can show that the error won't happen. Instead, I now document the preconditions in the summary / parameter descriptions and don't even mention what happens if they are not met. The idea is that people should not try to catch these exceptions explicitly anyway, so there is no need to document their types.

For documenting the preconditions, stating the obvious just creates needless clutter - if passing null to a method makes no obvious sense, the caller has to expect an exception if he passes null anyway, even if that is not documented. The same is true for the case of ObjectDisposedException - this interface is so widely used that someone calling Dispose would be aware of the responsibility to ensure nobody will continue using the object.

1

A reasonable rule of thumb:

  1. Catch any exception you can fix.
  2. Catch, comment on and rethrow any exception you can't fix but can say something useful about.
  3. Don't catch any exception you can't process or diagnose better than the default processing will.

You may want to have a top-level non-fatal exception handler to trap anything that couldn't be handled below to try to keep your application from falling over, but that's going to depend strongly on your particular application. For example, iOS applications prefer to trap as much as they possibly can; a command-line app may be perfectly OK if it traps almost no exceptions at all.

0

Even Java doesn't "document" all exceptions. Despite the requirement that every thrown exception be mentioned in a throws clause, any line of code can throw a RuntimeException without needing to declare it in the method signature.

2
  • This goes against popular advice though - specifically, Effective Java, 2nd ed, Item 62 is "Document all exceptions thrown by each method". In fact, Bloch acknowledges that unchecked exceptions are generally for programming errors, but that they should be documented carefully too, since that is "the best way" to document the preconditions of a method. I am not sure whether I agree though. If I document the execptions, I make them part of the interface contract and may have to add code to make them remain the same if my implementation changes.
    – Medo42
    Nov 23, 2012 at 9:41
  • 1
    For every expert, there's a counter-expert. Bruce Eckel, author of the rather famous Thinking in Java, was once in the pro-checked-exceptions camp, and has changed sides. There's a nice discussion between Eckels and Anders Hejlsberg, the creator of C#, which doesn't have checked exceptions. Nov 24, 2012 at 14:56
0

The standard way to do it is with the java approach. Programming errors should be unchecked exceptions and should not be caught to ensure a fast failure. Contract errors are checked exceptions and should be handled appropriately by the client.

2
  • 2
    Do you consider (eg) a calculated (or an error value of) NULL as a programming error or a contract error? I go with your distinction, but the boundary is not so clear cut :)
    – Andrew
    Nov 20, 2012 at 6:36
  • If the null is computed from an argument that's been passed in (such as a null argument, or something invalid), that's a contract error. If the null is computed because of a piece of bad code, that's a programming error. If the null is supposed to be computed, it's not an error. I don't really see any ambiguity in this example.
    – J.Ashworth
    Nov 22, 2012 at 1:40

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.