subbu.org

Exception Design and Usability

with 13 comments

Most of us consider exception management one of the simplest issues in programming. Since exceptions are easy to throw and catch in programming languages like Java, exception design rarely takes priority in design discussions. Not surprisingly, one of the least considered aspects of designing exceptions is usability. Yes, usability of the API being designed should be the primary focus when designing and implementing exceptions. This is more so when the software is layered, like most software today is.

Recently I had to spent several hours debugging some third-party software. Most of the issues I faced are due to misconfiguration and poorly explained/documented APIs. Sometimes exceptions did not make sense. Sometimes components did not work as expected, and left little or no clues. Some exceptions occurred much later than the cause, making it more difficult to connect the cause to the exception. I don’t think I am alone with this experience.

Most developers are also users of APIs designed by other developers. Are those developers so mean to other developers, and want them to suffer? I think the problem is due to a lack of attention to usability of their APIs and implementations.

Imagine you are making coffee with a coffee maker. The steps are very simple. You pour the beans, or ground coffee, you pour water, make sure it is plugged in, and switch it on. Within a few minutes, your coffee will be ready. What can go wrong in this process? Very few things can wrong with a coffee maker (assuming that the coffee maker is not a dollar store kind). The very few things that can go wrong can be fixed by doing things right, such as not forgetting to pour water, checking that the coffee maker is actually plugged in, etc.

Now imagine the same coffee maker in software, with an ICoffeeMaker interface like this.

public interface ICoffeeMaker {
public void plugin();
public void addWater(Water water);
public void addBeans(BeanScoup beans);
public void registerCarafe(ICarafe carafe);
public void makeCoffee();
public void plugout();
}

Let us write a simple coffee maker application using an implementation of the ICoffeeMaker interface.

ICoffeeMaker coffeeMaker = ICoffeeMakerFactory.getInstance().getCoffeeMaker();
Water water = new Water(4); // Four cups
BeanScoup beans = new BeanScoup();
coffeeMaker.addBeans(beans);
coffeeMaker.addWater(water);
coffeeMaker.plugin();
coffeeMaker.registerCarafe(myCarafe);
coffeeMaker.makeCoffee();

We could also add a listener that would alert the caller when the coffee is ready. If everything goes well, the coffee maker would pour the coffee into the registered carafe object, and call the listener when the coffee is ready.

Most likely such a coffee maker would be layered on top of several other components, such as a power source (IElecticPower), a heater (IHeater), water level sensor (IlevelSensor), and so on. Each of these components may in turn be layered on top of several other components, depending how those are implemented. It is quite reasonable to expect most of those components are sophisticated and have complex implementations with lots of features. It is also quite possible that those components have several knobs (e.g. configuration files), and setup requirements.

Let us get all the components required by the coffee maker, configure through some build files, write our coffee maker class, and compile it. We are now ready to make coffee. Before we run the coffee maker, let us see what can go wrong, so that we can handle those conditions properly and tell the user what went wrong.

If we follow the hardware version of the coffee maker, we could expect similar issues such as a NoWaterException, or a MissingCarafeException, or a NoPowerException. But as a user of the ICoffeeMaker, you would not expect any of the following.

  • A NullPointerException from the IHeater implementation.
    java.lang.NullPointerException:
    at com.heaterco.impl.electric.HeaterImpl.warm(HeaterImpl:1250)
    at com.heaterco.impl.electric.HeaterImpl.start(HeaterImpl:94)
    at com.coffeeco.impl.m1343.CoffeeMakerImpl.preMake(CoffeeMakerImpl:495)
    ...
    at com.coffeeco.impl.CoffeeMakerImpl.makeCoffee(CoffeeMakerImpl:234)
    ...
    

    There is nothing strange about this exception, except that it does not tell what went wrong, and what you, as a user of the ICoffeeMaker interface should do about it.

  • An XML parser exception from within a ILevelSensor layer.
    com.sensorinc.fluid.ConfigException: The markup in the document following the root element must be well-formed.
    at com.sensorinc.fluid.SensorImpl.init(SensorImpl.java:103)
    ...
    Caused by: org.xml.sax.SAXParseException: The markup in the document following the root element must be well-formed.
    at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:264)
    at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:292)
    at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:98)
    at test.Service.load(Service.java:53)
    ... 18 more
    

    This exception is slightly better than the previous one as it at least tells that some config file is misconfigured. It still does not tell you where the file is located in your classpath or the file system.

  • Here is another one, caused by the same misconfiguration.
    java.lang.NullPointerException
    at com.sensorinc.fluid.SensorImpl.init(SensorImpl.java:131)
    ...
    

    Here the misconfiguration of the sensor surfaced as a NullPointerException due to a coding error in SensorImpl. This one is harder to debug, and you will need the source of the sensor to figure out what went wrong. More lost hours.

There is a pattern in all these three examples.

  • None of these exceptions are part of the ICoffeeMaker interface. The user of the coffee maker would not expect any of these. But since software is malleable, exceptions like this can happen.
  • There is little info on how to fix the exceptions. The developer(s) of these components paid no attention to how exceptions are reported and propagated to callers of their APIs. In essence, they did not care deeply about usability of their software.

What is the remedy? If you are the user of the APIs, there is probably nothing you can do about it, other than getting used to those exceptions and learn by experience, or pick another API/implementation.

For people developing APIs for others to use, I would like to make a few suggestions.

  • Pay more attention to exception design and propagation. When propagating an exception, leave clues. Provide as much information as possible of the cause. Make the cause obvious.
  • Exceptions of part of the interface. So the exceptions must convey meaning.
  • Users of the APIs use exceptions to augment their understanding of the APIs. The more pleasant you make their experience, the more they will love your APIs and promote them. So, please be nice to your users.

If the API designers and implementors of the coffee maker components paid more attention, the stack traces would have looked like the following

  • com.heaterco.electric.NoPowerSourceException: Heater cannot be warmed. There is no power source connected. Check your power source.
    at com.heaterco.impl.electric.HeaterImpl.warm(HeaterImpl:1250)
    at com.heaterco.impl.electric.HeaterImpl.start(HeaterImpl:94)
    at com.coffeeco.impl.m1343.CoffeeMakerImpl.preMake(CoffeeMakerImpl:495)
    ...
    at com.coffeeco.impl.CoffeeMakerImpl.makeCoffee(CoffeeMakerImpl:234)
    ...
    

    In the original stack trace, the developer did not check if the power source is null before continuing.

  • com.sensorinc.fluid.ConfigException: Sensor config sensor.conf.xml misconfigured. The cause is "The markup in the document following the root element must be well-formed.".
    at com.sensorinc.fluid.SensorImpl.init(SensorImpl.java:103)
    ...
    Caused by: org.xml.sax.SAXParseException: The markup in the document following the root element must be well-formed.
    at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:264)
    at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:292)
    at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:98)
    at test.Service.load(Service.java:53)
    ... 18 more
    

    This exception atleast tells that there is configuration file sensor-conf.xml that is invalid.

  • I added the third exception to merely illustrate that exception propagation needs more attention. Here is the code snippet that probably caused the NullPointerException.

    Sensor sensor = null;
    try {
    // Load the configuration
    
    // Set config data on the sensor
    sensor = new ...;
    sensor.setId(...);
    ...
    }
    catch(...) {
    throw new ConfigException("Sensor " + sensor.getId() + " misconfigured");
    }
    

    Since the sensor object not initialized yet, we now have new exception masking the misconfiguration issue. This is a coding error. But trust me, this happens.

Since most discussion on exceptions include the checked vs unchecked question, let me say that unchecked exceptions are not bad. They just deempasize usability little more, because compilers do not enforce propagation or handling. Your kitchen could get flooded if the dam at the hydro-electric power plant crashes and the dam engineers used unchecked exceptions in their design.

  • Digg
  • del.icio.us
  • Google

July 31st, 2005 at 7:23 pm

Tagged with

RSS feed

13 Comments »

Comment by David Andriana at 2005-08-01 00:25:29

In order to avoid the last code snippet, I make programmers systematically use “final” variables, and if impossible, add comments in such a way that maintainers will not be tempted to set the variable “final” as a potential bug fix.

 
Comment by Rishi at 2005-08-01 00:26:38

Subbu

Not sure what point are u driving home.

Is it the use of sensible exception handling or is it the use of use defined exception rather than Java exceptions.

The article stayed well clear of the “checked vs unchecked” holy war.

Some of the points made were good, but I don’t think the article did justice to the topic. I think the article could do with a lil bit more meat.

 
Comment by tom at 2005-08-01 00:56:28

What I often find is that (my) libraries must mature, and improving exceptions and logging is a major part of that. IMHO what you actually are trying to bring is: make sure the errors (exceptions) make sense!

Often when a release 1.0 is in daily use it gets into situations which were not anticipated when developing the library. IMHO it is inpossible to anticipate all possible situations, so this is natural. Each situation then leads better handling of the situation.

My biggest concern in de last example is the fact that the original exception is lost; you do not know exactly where the exception occured.

 
Comment by Björn at 2005-08-01 02:41:20

If you avoid setting your sensor object to null in the last code snippet the compiler will warn you that your object migth not have been initialized. Perhaps it made sense in C but in Java it is best to wait with initialization of objects until they get the value for which they were intended.

 
Comment by Marc at 2005-08-02 01:11:19

Perhaps you should take this a step further with some more examples of a proper implemented API.

 
Comment by Paul Holser at 2005-08-02 10:28:41

“Most of us consider exception management one of the simplest issues in programming.”

Wow, really? IMHO it’s more that teams do not take exception handling into account at all when creating systems, rather than teams perceiving good exception handling strategies as vital to a system’s design. I sense that most teams perceive exceptions as nuisances that should be dispensed with as soon as possible, postponing and creating development headaches in the interest of short-term coding convenience.

 
Comment by Subbu Allamaraju at 2005-08-02 11:48:40

In response to Paul Holser’s comment -

Not sure if you are agreeing or disagreeing. The fact is that, most developers treat this issue quite easy, since it since it is very easy to throw and catch an exception in languages like Java.

 
Comment by Will Sargent at 2005-08-02 21:20:51

Great post. Relevant and absolutely correct.

 
Comment by Abhi at 2005-08-03 14:20:10

So the point made :-
Exceptions should be informative..
Define User Exceptions based on functional cause.. Should be reported by the interface.
And
Make the technical cause very obvious with proper messages and not swollowing the original stack trace.

 
Comment by Shailesh at 2005-08-29 23:25:38

Great point and I agree that I faced such types of problems even in very popular current framework. They need to print proper cause about the exception occured, as we can know in advance [by proper testing and good test cases] that what might can cause an exception in the given code, so it need to be properly documented and traced.

 
Comment by Wouter Lievens at 2006-01-04 06:54:07

It’s called Design By Contract…

 
Comment by sudhir nimavat at 2007-03-12 23:22:40

Ya. agree, i have faced similar problems while working with some of opensource libraries, they just throws unchecked NPE and with out any information and as a developer i had to debug actual code to find out the root of d exception,
gud API should never through exceptions without including information.

But recently spring is promoting unchecked exceptions wat abt tht?

sudhit
http://www.jyog.com

 
Comment by s at 2007-03-12 23:24:06

wheres my comment?

 
Name (required)
E-mail (required - never shown publicly)
URI
Your Comment (smaller size | larger size)
You may use <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> in your comment.

Trackback responses to this post