Saturday, 21 April 2012
Microsoft’s .Net Framework defines literally hundreds of exception types, and application developers can extend that list ad infinitum. But just a few have emerged in my experience as especially useful in clarifying my thinking and my code. These types fall into three broad categories: Precondition Violations, Contained Failures, and System Failures.
Nat Pryce dryly suggested a type from which these might ideally derive – “StupidProgrammerException”. They’re thrown when it’s clear that a software developer has done something wrong. Documenting these exceptions allows proactive developers to avoid causing them at all, and throwing them with descriptive messages allows experiment-oriented developers to grok the API’s semantics quickly.
The .Net Framework provides several handy exception types for reporting precondition violations:
- NotImplementedException indicates that the attempted operation is not supported by the current type. For instance, a partial interface implementation could provide real functionality for some methods and throw NotImplementedException from others. (Think ReadOnlyCollection.Add.)
- NotSupportedException indicates that the attempted operation is not valid on the current object. For instance, a read-only FileStream throws NotSupportedException if a client attempts to write.
- InvalidOperationException indicates that the attempted operation is not allowed due to the current state of the object (or the system it’s abstracting). For instance, attempting to execute a SqlCommand on a closed connection results in an InvalidOperationException, as might attempting to draw funds from an empty account.
Concession: Calling this a ‘stupid programmer exception’ is an over-generalization. Some invalid operations aren’t easily preventable at compile time and are best treated as contained failures, as discussed below.
- ArgumentException indicates that there is a problem with one or more parameter values passed to the attempted operation. But almost any error can be arguably attributed to some argument somewhere, so ask yourself, “Can this violation be detected by inspecting the arguments themselves – either individually, or in combination?” If so, use ArgumentException (or a subtype). If not – if the violation can be detected only by comparing the arguments against the state of the target component, throw InvalidOperationException instead.
Not all exceptions are preventable – even when we programmers manage to transcend our stupidity. It’s useful to divide the remaining failure modes into two categories – those that are specific to a particular operation or entity (which I’m calling contained failures), and those that likely affect whole swaths of functionality (system failures). Many, though not all, contained failures are directly attributable to user behavior.
I’ve collected a useful set of these exception types over the years.
- InvalidInputException indicates that a user’s input is flawed in some way. It is analogous to ArgumentException, but at a higher level of abstraction – targeting end users rather than software developers. It may report more than one error in a single instance, and should carry a localized message where applicable. Examples include letters being entered in a numeric field, or a required field being left blank.
- InvalidOperationException, as discussed above, indicates that the attempted operation is not allowed due to the current state of the object (or the system it’s abstracting). It’s often preferable to provide the client a means of checking this state before making a call, but sometimes it’s most efficient to allow an attempt and then report any failure. When InvalidOperationException is used this way, the user-friendliness concerns described under InvalidOperationException may apply here as well.
- AccessDeniedException indicates that the current user does not have permission to perform the requested operation. Usually some form of audit should be written when this exception is thrown or caught.
- ItemNotFoundException indicates that a request affecting a single, specific object (usually by some unique identifier) failed to locate the object. The exception’s message should clearly state the criteria of the query that came up empty.
- DuplicateItemException indicates that a request to create a new object failed because the object (or some part of it that ought to be unique) already exists. This exception’s message should clearly communicate the points of duplication.
- ConcurrentUpdateException is thrown when two or more mutually exclusive requests were received simultaneously. Typically, one request will be honored, while the other(s) will fail with this exception. ConcurrentUpdateException is closely linked to the optimistic locking pattern.
- ResourceInvalidException indicates that problems have been encountered in data coming into the system from a repository or feed. The problem could be caused by flawed persistence or translation logic, or introduced be manipulating data manually. An example would be records in a relational database that have no valid equivalent when mapped into an object model. The key here is that the problem appears to be isolated to a particular entity.
- ConfigurationException (or ConfigurationErrorsException) is the .Net Framework’s way of telling us when system settings (usually global) are unusable. These errors usually occur immediately after deployment, and require engineering intervention for correction.
- ResourceUnavailableException, another custom type that I use often, indicates that a some vital dependency of an application or component is non-operational or cannot be reached. Examples might include a remote server that won’t respond, or a discrete subsystem throwing unexpected exceptions.
Of course, all this is just the beginning. It’s one thing to throw the right exceptions; it’s another to catch and handle them effectively. And isn’t that the real point? The goal of throwing the right exception is to allow the application to contain the damage and enable the solution. But that’s a topic for another day.
What exception types do you find the most useful?
Saturday, 27 March 2010
Given: business software developers are people finding a better way* – we’re writing software to help improve the business.
A cost center improves by spending less. Therefore, the impact of our software development effort approaches a limit of zero. The law of diminishing returns kicks in, and effectiveness decreases logarithmically over time.
A profit center improves by earning more. Therefore, the impact of our software development effort approaches a limit of … infinity (theoretically, at least).
This is a gross over-simplification, but the dynamics are real.
* Apologies to Dana Corporation. My dad always said the slogan on their trucks reminded him of me.
Saturday, 27 March 2010
The Amway salesman had a simple answer: “$-U-C-C-E-$-$”. We took issue on a meaning-of-life level, but I digress.
The software developer says the system is running as specified. The Enlightened software developer says the system is running as the business owner and domain experts say it needs to.
But leave it to the business analyst to point out that a crucial group of users still have no idea how to use the system. Should circumstances beyond our sphere of control affect how we rate our status?
Eric Evans woke me to this with his discussion of upstream/downstream relationships between bounded contexts. Using his criteria, a system/project/team is upstream from another if (and only if) it can provide real value independent of the related system/project/team. If my application is up and running, and all of its run-time and batch-process dependencies are fully functional, but it exists solely to provide data or behavior to an application that has been canned, nobody cares about my success.
Or putting it another way…
If an application is running in the forest, but there’s nobody there to use it, does it really make its SLA?