No Exceptions made

In response to a finding during an internal project code review last Friday and an article in the latest Java Magazine we had an interesting discussion on the reasons for using exceptions. Eventually I supported two rules of thumb, one I had thought of myself, the other from a colleague of mine.

The following rules of thumb should be seen as in the context of a development platform that supports their use and a developer who uses as the following, basic rule for recognizing the situation to use an exception: use an exception if you cannot follow the normal execution path any more, in other words, if something extraordinary happens or has happened.

Additional rules of thumb
The basic rule above seems like a logical assumption, since the name Exception seems to indicate just that. Yet I think it is too naive and the following rules of thumb should be used as well:

1. Do not use exceptions and if you still think you have found a reason for using an exception, think it over again.
2. Do not use exceptions in an (object oriented) domain model.

Obviously these rules of thumb deserve some reasoning to support them.
Firstly, throwing an Exception is not without its cost:

  • The runtime execution engine often uses some extra execution time to throw and handle an exception.
  • An exception interrupts the normal execution path, especially if it is not directly captured by the calling method. This means that it is impossible to guarantee that invariants are enforced. You will need a transactional kind of mechanism to rectify the situation, otherwise you must assume that the runtime is corrupt and that it should be discarded.
  • It means an additional return from a method, an additional path to follow. It increases the complexity of a method
  • The try-catch mechanism that is necessary to correctly capture an exception takes several lines of code and distracts from the actual meaning of the code.
  • It basically means that an action initiated by a user or other actor has been stopped and cannot be completed. At best, it can be attempted again, in the worst case, the initiator needs to repeat the attempt himself.

These costs lead to the first rule of thumb. If you can prevent using an exception, everything else being equal, then that should be preferred. I think there are some situations where at first sight, it seems logical to throw an exception, but there are better alternatives:

Pre-conditions for arguments should be set declaratively
A method can throw an exception if the arguments with which a call is made, do not meet the requirements. In other words, an Exception is used for the enforcement of pre-conditions on the arguments. This is better done by declaring those pre-conditions in an explicit way as part of your method signature. By having arguments only in a valid state, you force the calling method itself to ensure a correct call. When this calling method is implemented in the same manner, these pre-conditions will then bubble up, so they are declarative and pre-conditial on the external interface of the unit or code under design.

In other words, they are in the form of a (code) contract. If the external interface is a web service, for instance, and the contract is coded in XML schema, a call breaking pre conditions is already rejected by the schema validator, without invoking the underlying runtime and therefore without possibly entering an invalid state.

Pre-conditions on the internal state should be handled more elegantly
You might say that a call can only be invoked when a certain internal state is in effect. For example, using what came up during our discussion: a teaching module contains slides. The module may only be discarded if the slides have already been removed from the module. This requirement means that the calling method must be able to determine this state (so encapsulation of inner state is lost) and the possibility of race conditions also remains: a slide was added just before the module was supposed to be removed.

In such a situation it is better to look for other possibilities. In this case, start with the question: why should the slides actually be removed first? Suppose the answer in this case is: because the slides may also be used by other modules and so if they are just removed automatically, a user may unwittingly remove content from another module.
It generally turns out there are better alternatives. In the current case it is actually clear that slides are aggregates themselves and that they should not be removed with the learning module any way, but only the links may be removed. Furthermore, you should offer an additional option for orphaned slides to be found and deleted manually or automatically (garbage collection).

Invariants on the internal state can be better enforced by using locks or no threading
When execution has already started and the internal state is changed during this by another thread, the best way to handle this is the use of locks: this prevents problems from occurring at all and enables both execution paths to be followed (sequentially). Locks have drawbacks too, of course (unclear code, deadlocking). Of course, the use of an actor model for concurrency will tackle these problems at the root.

Remaining: pre-conditions and invariants on the state of the external environment
Obviously a certain situation in the use of exceptions still remains: the state of the (external, non-influenced) environment does not satisfy all conditions before or during execution. The canonical example here is a database that is not up at the start, or goes down during the execution of a request. This remains a good reason for throwing an exception.

Obviously, the use of these exceptions could be countered as well, by taking on a greater responsibility, so it includes the handling of more situations the environment could be in. For example, a unit could take on the responsibility of synchronizing the current client state with a server eventually, instead of needing it to be synchronized instantly. This would make it possible to handle a network that is not available by persisting the client state until the network is available again, without throwing Exceptions.

A (good) domain model has no dependencies to an external environment
As the external environment remains the only reason to throw an Exception, it naturally follows that a domain model has no reason to throw an Exception, as it should not depend on any external environment. A good domain model should be a pure representation of the real world, as seen through the lens of a domain. This real environment is never dependent on a database or network system in the system under design. Of course, the domain itself could be concerned about databases and network connections, but those would then be included as domain objects in the model only.

By applying these rules of thumb for using Exceptions it is possible to make systems more robust, simpler, faster and more useful. Obviously, the work involved in making a better design may not always deliver enough rewards to warrant it. However, within the heart of your application, the domain model, this would surely have to be the case, unless your application as a whole has little added value. But building it might not be a good idea anyway then.

Written by Rick | Tags: , , , , , , |

No Comments - Leave a comment »

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress | Aeros Theme | TheBuckmaker.com WordPress Themes