Friday, 13 April 2012

Runtime Exceptions: what is the true cost of reliance upon them?

Abstract

It is a common belief that the .Net framework provided System.Exception and its derived classes should be avoided when they are not absolutely essential, and that instead; meaningful, alternate, less expensive solutions should be sought out and applied. Expense in this instance refers to both the physical cost of resources (RAM, processing cycles, etc.) and the fiscal cost of maintaining and developing against code which utilises Exceptions.

Although there exist articles and publications which appear to recommend the use of Exceptions, this interpretation is misguided. Exceptions do have a run time cost, and they do have a development cost, this essay finds that avoidance of Exceptions is the preferred development technique, and that there are various industry standard Patterns for achieving this end.

Caveat

This essay starts from a position which at first might appear ‘off track’, but I encourage you to persevere so that the rationale for such a starting point can be appreciated when the context becomes clearer.

Introduction

In Java, one has the option of using either a Checked, or an Unchecked Exception; The difference being noticed by the compiler as “A Checked Exception is derived from java.lang.Exception, whereas an Unchecked Exception is derived from java.lang.RuntimeException”. There are more differences than this essay cares to note however, and the notion is only introduced to enable discussion of Checked Exceptions.

As previously noted, the exception type is differentiated by the compiler. The compiler behaviour which we are currently interested in is; the enforcement of the Checked Exception being explicitly handled at some point in the object graph, for example:

// The Checked Exception
public class InvalidDataException extends Exception { … }

// A member in some class which throws the exception
public void DoSomethingWithData(String data) throws InvalidDataException { … }

// A member in some other class which uses DoSomethingWithData
public void UseDoSomething1(String data) throws InvalidDataException
{
    DoSomethingWithData(data);
}

// Another member in the class which uses DoSomethingWithData
public void UseDoSomething2(String data)
{
   try { DoSomethingWithData(data); }       
   catch(InvalidDataException e) { … }
}

// A member which uses UseDoSomething1
public void UsesUseDoSomething1(String data)
{
   try { UseDoSomething1(data); }
   catch(InvalidDataException e) { … }
}

// A member which uses UseDoSomething2
public void UsesUseDoSomething2(String data) { UseDoSomething2(data); }


The point which is pertinent to this essay is that the intelli-sense of most Java IDEs will inform the programmer, prior to compilation, that there is an unhandled exception; either that, or during compilation (for example when compiling from the command line) the compilation will fail and the programmer is notified that there is an unhandled exception.

Part of the rationale behind Gosling, Chan et al. implementing the Checked Exception pattern was that
‘… when methods do not declare what exceptions they throw (unchecked exceptions), it becomes more difficult to handle them …’
And that …
‘… Unchecked exceptions make it easier to forget about handling errors, since the compiler doesn’t force the developer to catch or propagate the exception.’
There are obviously counter arguments to only implementing Checked Exceptions, and for this reason the team at Sun also implemented Unchecked Exceptions in the JRE.

The .Net guys at Microsoft for various reasons elected not to implement Checked Exceptions –there is of course nothing stopping anyone else from developing a library to make this provision, though no one has made such publicly available –this could possibly be indicative of the reasons behind the commonly held view that ‘Unchecked Exceptions should be favoured over Checked Exceptions.’

The .Net team did however follow Sun’s lead and provided a means for the developer to document their code inline, which is subsequently automatically incorporated into the intellisense as provided by the Visual Studio IDE. This [documentation] is the only means by which a .Net developer is informed that an Exception can be thrown by a member which they are using. This in itself even is a powerful tool, which should stop the proliferation of non-exceptional exceptions, i.e. exceptions which are thrown when there really is no need to do so; which now brings us on to the subject of ‘what is an exception?’ The definition of which can be found in many sources … http://docs.oracle.com/javase/tutorial/essential/exceptions/definition.html for example states the following:
The term exception is shorthand for the phrase "exceptional event."

Definition: An exception is an event, which occurs during the execution of a program that disrupts the normal flow of the program's instructions.
This phrase is quite important in our analysis: “that disrupts the normal flow of the program's instructions“, because it then forces us to consider what the normal flow of the program’s instructions actually is, which in turn forces us to consider whether or not the case with which we are dealing is indeed an exception or is it expected behaviour –just because something does not occur regularly does not mean that it is not expected behaviour.

It is possible to conceive of the situation whereby one object might consider that something would cause an abnormal flow to the program, whereas another object in that very same program might consider that same thing as perfectly acceptable, for example: A delivery object might consider that a set of data which represents an order is not valid when there is no customer associated with the customer id of that order item. It is possible then to conceive that the delivery object could throw an exception when it attempts to match an order to a delivery address if, for the sake of example, the address is itself obtained by linking order to customer to address. However, perhaps another part of the application is responsible for providing historic order reports: this object might not actually care that there is no customer data matching the customer id which forms part of the order’s data, because this object’s function does not rely upon those data. The question then becomes ‘who is responsible for notifying the system that an exceptional data set has been retrieved from the database?’ Consider the following implementation of the proposed example.

If HistoricOrderReporter does not consider the missing data an exception and yet, DeliveryObject does, then it must be clear that the exception cannot be thrown from any of the IOrder, IDataAccess, or IRepository implementations, and it is therefore the responsibility of the DeliveryObject to throw the exception -if even exception throwing is deemed the appropriate route to take!

What this example brings to light is the tenet that it is very important that when an exception is deemed necessary, it must be thrown at the appropriate point; which then beggars the question “at which point is it appropriate to throw an exception?”

Enter the Separation of Concerns (SOC), which is all about … Who does what? And what does who care about?

In our cited example, the HistoricOrderReporter does not care about whether or not the referenced ICustomer exists, and neither does either of our implementations of IDataAccess.

What we have just identified is a business rule. It is a rule of the business that: In order for us to process the delivery (amongst other things) of an order, all unfulfilled orders must have a valid customer reference, and in order for a customer reference to be valid, there must exist in the data store a customer who can be identified by the given customer reference.

Imagine then for a moment that we know about the existence of this business rule in advance of coding our application.

We have identified a set of objects which we are implementing as part of our solution, we have not arbitrarily divided up the functionality, but instead we have obeyed the SOC principle. We have created an IDataAccess to take care of accessing our stored data and by so doing (in conjunction with our IRepository), we have abstracted away from our IHistoricOrderReporter and IDeliveryObject where those data are physically stored (For discussion of the Repository Pattern see Patterns of Enterprise Application Architecture, Fowler, M). We have an ICustomer which only cares about the data which describe a customer; we have an IAddress which only cares about the data which describe an address; and finally we have two operational objects, IHistoricOrderReporter and IDeliveryObject who are only concerned with the data which represent historic orders and delivery details respectively. One could argue that this division of responsibilities is complete, and is logically sound.

Now we need to consider the implementation of the required functionality, bearing in mind that it is possible for us to request Customer data from our IRepository when there are no data corresponding to the specified Customer Id.

To continue with the example, we should consider our options:
  1. What does our subsequent code look like if we throw an exception from our IDataAccess implementation?
  2. What does our subsequent code look like if we throw an exception from our IRepository implementation?
  3. What does our subsequent code look like if we throw an exception from our IDeliveryObject implementation?
    • Is our code cleaner?
  4. Who (in terms of code) is affected by throwing an exception in our IDataAccess implementation?
    • Is our code cleaner?
  5. Do we even need an exception at all?
    • What are the implications of throwing and of not throwing an exception?
    • What will be responsible for acting on our thrown exception?
    • How will anyone know that an exception could be thrown?
There are various answers to the above questions, all determined by; what the application does, how the application runs, who uses the application, etc., which almost alludes to the idea that there is no “general rule” that we can apply here.

Throw exception in IDataAccess

public class DatabaseAccess : IDataAccess
{
    public ICustomer GetCustomer(int id)
    {
        var sql = string.Format("SELECT * FROM customers_table WHERE id = {0}", id);
        using (var connection = GetOpenedConnection())
        {
            using(var command = connection.CreateCommand())
            {
                command.CommandText = sql;
                using(var reader = command.ExecuteReader())
                {
                    while(reader.Read()) { return reader["some field"]; }
                    throw new CustomerNotDefinedException();
                }
            }
        }
    }
}

The very first question that comes to mind when looking at this implementation is … “why throw an exception just because a record does not exist?” We could reasonably imagine that there will be a case where the data access component is asked to retrieve the data for a customer, and yet there is no customer with the specified id, for example an operator might have entered the wrong number via a UI, etc.

However, we remember our business rule (mentioned earlier) and imagine the case where we have an unfulfilled order with an associated Customer Id, and the customer with that Id is no longer stored in our data layer (we are ignoring the fact that normally, our database should be performing referential integrity checks on our unfulfilled orders); because we need to satisfy our business rules, we have decided that the exception must go here. So … the IRepository does not know what to do in the case of exception, and also it really doesn’t care at all that an exception was thrown, and so error handling is not required:

public class Repository : IRepository
{
    private IDataAccess _database;
    private IDataAccess _fileSystem;
    private IDataAccess Database
    {
        get { return _database ?? (_database = new DatabaseAccess()); }
    }

    private IDataAccess FileSystem
    {
        get { return _fileSystem ?? (_fileSystem = new FileSystemAccess()); }
    }

    public IEnumerable<ICustomer> Customers { get { return Database.GetCustomers(); } }

    public IEnumerable<IOrder> Orders { get { return FileSystem.GetOrders(); } }

    public ICustomer GetCustomerById(int id) { return Database.GetCustomer(id); }

    public IOrder GetOrderById(int id) { return FileSystem.GetOrder(id); }
}


But that is not the case for our implementations of both the IHistoricOrderReporter interface and the IDeliveryObject interface. They will both need to handle the CustomerNotDefinedException when it is thrown, however how they handle that exception is largely idiosyncratic. For example the DeliveryObject needs to abort (gracefully) and the HistoricOrderReporter can carry on regardless, and so we find something akin to the following code in those classes:

public class DeliveryObject : IDeliveryObject
{
    public IRepository Repository { get; private set; }
    private void SomeMethod()
    {
        foreach(var order in Repository.Orders.Where(o => !o.Fulfilled))
        {
            try
            {
                var customer = Repository.GetCustomerById(order.CustomerId);
                // do stuff with customer
            }
            catch(CustomerNotDefinedException cnd)
            {
                // bail out gracefully
            }
        }
    }
}


The code for DeliveryObject is pretty much what we would expect under these circumstances; the Repository is queried for an ICustomer who may or may not exist, and the process needs to terminate when the requested customer does not exist.

public class HistoricOrderReporter : IHistoricOrderReporter
{
    public IRepository Repository { get; private set; }

    private void SomeMethod()
    {
        foreach (var order in Repository.Orders.Where(o => o.Fulfilled))
        {
            try
            {
                var customer = Repository.GetCustomerById(order.CustomerId);
                // do stuff with customer
            }
            catch (CustomerNotDefinedException cnd)
            {
                // erm ... I need to carry on with what I was doing
            }
        }
    }
}


However, the code for HistoricOrderReporter can suddenly become quite convoluted in order to continue processing; because, even though the IDataAccess has thrown an exception, quite frankly, HistoricOrderReporter does not care about it at all and can carry on regardless –but the exception has to be caught otherwise the process will terminate and the exception will be propagated all the way up the stack until the next exception handler is found. More to the point, the programmer might not even think that a try {} catch {} block is required here, because s/he knows that it is perfectly reasonable to ask a Repository for some data and that those data do not actually exist within that Repository.

Suppose for a moment that the programmer of the HistoricOrderReporter is not aware of the possible CustomerNotDefinedException propagating up from IDataAccess; in fact the programmer is not even aware that there is an IDataAccess in the picture, because as far as s/he is concerned they are talking to an IRepository –this point should contextualise the opening few paragraphs of this essay. With the absence of Checked Exceptions from the .Net framework, the only way for a programmer to be aware of the possibility of an Exception being thrown by any member is documentation. There are many reasons why we should not use inline documentation for our code (cf. Clean Code; Martin, R.)

The programmer working away in blissful ignorance then, codes up the “SomeMethod” member without any exception handling, and the application is run. The application in this case does not have any exception handling except for the infamous catchall right at the top (perhaps in Main) … the application dies unexpectedly, due to a non-existent customer who is referenced in an historic order. But how will we know this? How can the application know (within Main) that this exception is not actually an exception, but is rather just a case of un-synchronised data? How can the application know what to do in the face of a CustomerNotDefinedException?

There are several lessons to be learned from the above scenario …
  1. Exceptions should not be thrown early, just because knowledge from the business rules implies that an exception will most likely eventually be thrown further up the stack.
  2. Business rules do not belong in an application’s data access logic.
  3. All exceptions should be handled as close to the source as is possible.

Throw exception in IRepository

All we would do in this case is move the logical point of throwing the exception one echelon up the object graph. The lessons to be learned from this are exactly the same as those if we were to throw the exception from IDataAccess. A discussion of both cases can be found in Bob Martin’s Clean Code; G17: Misplaced Responsibility, pp295

Throw exception in IDeliveryObject 

If we chose to implement our code such that the exception is thrown by the IDeliveryObject implementation, perhaps DatabaseAccess would look something like this:

public class DatabaseAccess : IDataAccess
{
    public IEnumerable<IOrder> GetOrders() { return new List<IOrder>(); }
    public IOrder GetOrder() { return null; }

    private Connection GetOpenedConnection()
    {
        var connection = new DatabaseConnection();
        connection.Open();
    }

    public ICustomer GetCustomer(int id)
    {
        var sql = string.Format("SELECT * FROM customers_table WHERE id = {0}", id);
        using (var connection = GetOpenedConnection())
        {
            using(var command = connection.CreateCommand())
            {
                command.CommandText = sql;
                using(var reader = command.ExecuteReader())
                {
                    while(reader.Read()) { return reader["some field"]; }
                    return null;
                }
            }
        }
    }
}


DeliveryObject would look like this:

public class DeliveryObject : IDeliveryObject
{
    public IRepository Repository { get; private set; }
    private void SomeMethod()
    {
        foreach(var order in Repository.Orders.Where(o => !o.Fulfilled))
        {
            var customer = Repository.GetCustomerById(order.CustomerId);
            if (customer == null)
                throw new CustomerNotDefinedException(); 
        }
    }
}


And HistoricOrderReporter would look very similar to this

public class HistoricOrderReporter : IHistoricOrderReporter
{
    public IRepository Repository { get; private set; }
    private void SomeMethod()
    {
        foreach (var order in Repository.Orders.Where(o => o.Fulfilled))
        {
            var customer = Repository.GetCustomerById(order.CustomerId);
            if (customer == null) { continue; }
            // continue processing customer
        }
    }
}


In this case, there is no exception handling required (in terms of the object graph) below DeliveryObject, and since HistoricOrderReporter is in effect a sibling and not an ancestor of DeliveryObject, it does not need to handle something which it considers to be normal behaviour as an exception.

The code is a little cleaner than when throwing the exception from within DatabaseAccess implementation, but it is only superficial, since all of the exception handling is only postponed for a single ancestral link in the object graph.

Further, this approach has not eliminated entirely the possibility of throwing the CustomerNotDefinedException -something, which we have seen demonstrated, could lead to trouble further on down the road. So perhaps we should be asking ourselves at this point, whether or not throwing an exception is actually the right thing to do.

Scouring through the wealth of literature available both online and in print, one will find many such rules as “Use Exceptions, Rather Than Return Codes”, “Don’t Pass Null”, “Don’t Return Null”, and so it is clear that this solution is unsatisfactory. Do we need to go back to throwing an exception from DeliveryObject?

Another approach to consider is the Special Case Pattern (Patterns of Enterprise Application Architecture; Fowler, M. pp. 496), whereby we create a class or configure an object so that the special case is handled. As it happens we don’t have to write more code (in terms of creating a special case object), but rather we could modify the GetCustomer() member of IDataAccess to return an IEnumerable of type ICustomer instead; and the usage code would be something akin to:

Repository.GetCustomerById(order.CustomerId).ForEach(CustomerProcessingDelegate);

This would be fine for the case where we just need to carry on regardless of whether or not an item was returned by our request to the Repository. It would be unsatisfactory however in the case where we do actually need to perform some function when no record can be found, such as log the missing data for example. In which case (i.e. only where this is necessary) the handling of such a call could be akin to the following:

var customerList = Repository.GetCustomerById(order.CustomerId);

if (customerList.Count() == 0)
    throw new CustomerNotDefinedException();


… And again we find ourselves asking, is it even necessary to throw an exception here? What, if anything, is preventing us from simply invoking the required procedure (logging, email alert, or whatever) and carrying on? Why do we need to abort the current task? Is throwing an exception going to give us anything above and beyond the functionality we have already provided? As implemented, the stack is in a very stable state, so why should we abort everything? What will be gained from this course of action? It is beginning to look increasingly like we should not be throwing an exception at all.

Who is affected by throwing an exception in our IDataAccess implementation?

We have already identified that IDataAccess is the wrong place to be throwing an exception, and so this is a moot point, and no purposes are served by reasoning out the answer to this question, but it should be remembered that everything above the IDataAccess instance in the object graph is a potential handler for the exception and also a potential candidate for causing a hard to track down “bug” –not because the exception is thrown ipso facto from a data access layer, but because the code under development is not part of a public API, and therefore there is no inline documentation, and therefore anyone using the data access layer could inadvertently misuse it, though misuse in this context could equally be seen as perfectly reasonable use. Further to this, how often do we, as developers, read documentation for an individual member on a class? Especially when the member has been named well and describes what it does?

Do we even need an exception at all?

In our efforts to address the previous questions, we have already answered this, though not explicitly. The explicit answer is “No, we do not need to throw an exception at all”, but let’s examine this further.

Why do we consider raising an exception?

We should only even consider raising an exception (according to our definition at the beginning of this essay), when
 …an event … occurs during the execution of a program that disrupts the normal flow of the program's instructions.
In our example we have alleviated these circumstances by making the disruption (if indeed it actually was a disruption) a part of the normal flow of the program’s instructions. We know that there will be instants where we ask a Repository for data and there are none. This is not a disruption at all, ever! This is how a repository works; we enquire of it “do you have these data?”, “please can I have these data?”, and we instruct it “store these data”, “update these data”, etc.

As an analogy: the local public library is a Repository. We go to the library and ask the librarian, “Can I borrow a copy of Agile Principles, Patterns and Practices, by Robert Martin, please?” When the librarian fails to locate the book, the library is not closed immediately, and the police do not turn up demanding that “everybody stop what they are doing right now and come with us” but instead, the library remains open for business, and the librarian informs us that “I’m sorry, but we don’t have a copy of that book”. We are then free to carry on with the rest of our day.

A lack in the existence of the requested data in our example does not render the application in a state such that it cannot continue; it does however mean that a delivery cannot be arranged; it also means that we have no idea who to contact with relation to this order, but as far as the software is concerned, all is well. The correct course of action then is
  1. Make a note of this data error.
  2. (If applicable, make someone or some other process aware of the data error)
  3. Carry on with what was being done.
  4. (If applicable, don’t attempt to process this item again).
Under these circumstances there is no need to throw an exception.

We consider throwing an exception because it has been advised: It is true that there exists literature, which does appear to advise throwing exceptions; examples which could be misinterpreted as such advice can be found here from Microsoft: http://msdn.microsoft.com/en-us/library/ms229030; or here (again from Microsoft): http://msdn.microsoft.com/en-us/library/ms229009; The former is a discussion on Design Guidelines for Exceptions, and the latter a discussion of Exceptions and Performance.

Just taking these discussions at face value, one could be forgiven for coming to the conclusion that we should throw an exception in our example code, but there is nothing in either of these discussions which explicitly tells us that exceptions are fine to use arbitrarily; but rather they suggest the use of exceptions for disastrous events, and discuss alternate options available to the programmer. In Framework Design Guidelines, Cwalina K, Abrams B pp 186, it is recommended that the programmer should
“… not use Exceptions for normal flow of control, if possible. Except for system failures and operations with potential race conditions … designers should design APIs so that users can write code that does not throw exceptions.”
For example the Microsoft recommendation (in the latter article, and also Framework Design Guidelines) is to implement the Tester-Doer pattern. Jeffry Richter, a recognised and respected authority on .Net programming advises though that this pattern is dangerous in that
“The potential problem occurs, when you have multiple threads accessing the object at the same time.”
Another recommendation is the Try-Parse pattern, a pattern endorsed by Jeffry Richter because
“It solves the race condition problem and the performance problem.”

Summary

The Microsoft sanctioned advice in Framework Design Guidelines is that
“In designing frameworks it is important to use exceptions as your error handling mechanism … In the end it will make your and your customers’ lives easier.” (pp.205)
Many developers have misinterpreted this as “it is important to use exceptions as your error handling mechanism”. Many developers have also therefore misinterpreted data errors as Exceptions.

From the sample code we have developed, it should be clear that when an exception is thrown it is the responsibility then of every object further up the object graph to guard against that exception, and that this is difficult to achieve in the absence of Checked Exceptions.

Because it is difficult to guard against all exception types, the usual catch-all will generally just look for System.Exception, rather than any sub-class and consequently will not know how to proceed; Further, when an exception has reached this level (in the object graph) the object on top of the stack is usually several pops distant from the object whence it originated, and therefore the process has no alternative to aborting.

When an exception is thrown, a new object is created and is populated with a Stack trace, and any further information as dictated by the implementer. This is a costly exercise, especially when compared with returning an empty array as IEnumerable. It could be described as wasteful when the exception is not even used for anything above application program flow –which incidentally is something strongly recommended against.

There are in existence multiple industry accepted Patterns which address the issue of error handling and Exception avoidance, and we have looked briefly at the Tester-Doer pattern and the Try-Parse pattern as alternatives to Exceptions. We have also looked at the importance of reporting an error from the right place, which can be identified by applying the Separation of Concerns.

No comments:

Post a Comment