Daniel Pitts’ Tech Blog

Posts Tagged ‘Object Oriented Design’

Levels of Error Handling

Sunday, November 29th, 2009

If you have been using computers for any amount of time, you’ve come across an error message.  From nearly useless “access violation” dialog boxes, to the more helpful “User names must not contain spaces” form messages, error messages are frequent on computers.  Whether you’re a Mac, PC, Penguin, or Other, error messages that mean only “something broke” are frustrating.

Programmers often treat all kinds of errors the same, especially when a user interaction is involved.  The problem is, not all errors have the same “meaning”, and not all errors should be handled the same way.  If, as a User, I attempt to use the program for something, and it reports a message target at a programmer (displaying a stack-trace, for instance), it can be confusing.  As a High-Level programmer, if a library call fails but my code gets back few details (getting only ‘There was an SQL error’, without the SQL statement that caused the error), it can be just as frustrating.

What do you mean by low-level or high level?

There usually different “levels” to an application, and each level has a specific type of concern.  Errors on lower levels should be filtered up in as appropriate a manor as possible, so that the higher levels can handle them as gracefully as possible.  Applications can have many different layers, but conceptually they can usually be represented in exactly four levels. What are those four layers? I’m glad you asked, because I’ve listed them below, from highest to lowest.

The User
This, of course, is the person using your application. Most often they are a human being, but someday users may include Artificial Intelligence or Extra Terrestrial entities.  Users are often task-oriented, although some of them are naturally explorers.  Task-Oriented users are more damaged by unexpected errors. Exploring users are more interested in seeing what is and isn’t possible, so errors often define a boundary for them, but these users are also most likely to find bugs in your application.
High-Level Code
This is sometimes called the “Domain” level.  The public methods in these classes are often task-oriented, and the class often define nouns that the User would be able to identify with.  Code in this level translates between user requests and low-level requests. This is of course an idealized definition, but the closer to this definition you can make your reality, the easier to maintain your application will be.
Low-Level Code
This is the library or framework code.  Code in this level usually is used to handle resources, and to actually get things done.  Most Low-Level code is “possibility” oriented. The API defined allows you to do simple, atomic, operations that might not be meaningful on their own, but can be strung together in a useful manor.
Resources
These are “external” things used by the application. Some examples are memory, files, databases, and sockets . Consumable (memory, disk-space, etc…) resources can run out unexpectedly, even if you “check” before hand.  Many resources fail for various reasons, for example sockets might fail because of a disconnected wire, files might fail because of a physical disk error, and database operations might fail due to overloaded CPU or low available memory.

Note, that there can be some “blurring” of levels, depending on the project you are working on.  For example, the developer of an HTTP Client library is probably writing “high-level” code, in that it represents the domain of HTTP, and provides “task-oriented” interfaces.  However, when that HTTP Client library is used in an application, it becomes “low-level”, because the User probably doesn’t care about the HTTP Protocol, and instead only cares about “downloading a file” or “viewing a web-page”.

From my experience, each of the levels correspond to a different kind of error, and those errors should all be handled differently. The likelihood of most of the types of errors can be managed, but not eliminated completely.

Error Handling

Handling User errors

This is the highest-level error.  It is caused by a user doing something they shouldn’t.  Most commonly this means the user entered an invalid input somewhere.  Whenever possible, the User Interface should be designed to prevent user errors.  For example, if you need a date then you provide a “Date Widget” and if you need a number you disallow non-numeric input.

Sometimes, it isn’t feasible to simply “prevent” user error.  In this case, the user input must be validated.  The best place to validate it is in the High-Level code.  Since this code is closest to the user, it has more knowledge about the user.  The High-Level code knows whether to pop-up a dialog box, print a line to the console, or update a form with error messages.

It may sometimes useful abstract the “error reporting” interface, so that it can be reused by different  UI interfaces. For example, domain level code might be used in a Web App, as well as a native GUI app, or even a command-line app.  Having an reusable interface for reporting multiple user errors helps in porting to new user interfaces.

Usually, it should be considered a bug in the high-level code if invalid user input causes a lower level exception. Low-Level code must verify the input it receives from higher-level code.

There are times, however, when it is infeasible to verify before hand whether an input is valid, and the validation must be handled a lower-level code.  In this case, the lower-level code should report the error to the higher level code, which should handle it and pass that information on to the user in an appropriate manor.

Handling Bugs

Bugs can happen in either low-level code, or high-level code.

In High-Level code, it means that some method (often a user task-oriented method) has violated some constraint, or failed to handle a lower-level error. In Low-Level code
it means that a method (part of a framework or library) has violated some constraint, or failed to properly handle a Resource Error.

First and foremost, it is rarely appropriate to show all the detail of “bug” caused errors to the user.  Most users would prefer not to experience bugs at all, but because this is the real world, we have to handle the bugs some how.  Most times, its enough to tell the user “There was an internal error.  Click ‘Report’ to send a bug report.”  The bug report should include any useful state information, but absolutely must include a full stack trace.  If possible, the high-level state should be recoverable, so that the user doesn’t lose any of their work, and can try either a slightly different approach, or wait for a bug fix.

The number of errors of these kinds can be reduced by Unit Testing, improving API design, Integration Testing, and User Testing.

Resource errors

These are Exceptional Circumstances.  Resource errors can be caused by bad data, connectivity issues, database constraint violations, file corruption, file-not-found, out-of-memory, and many more uncontrollable problems.

Resource errors should be reported to the user with as much detail as is likely to be useful to them.  If the user specifies a file, and that file is in the wrong format, this could be treated as a user error, and the user should be informed of the file they chose, and what was wrong with it.  Low-level code should report to higher-level code something along the lines of “I expected a FOO file,” and the higher-level code should report something like “The file c:\my.bar is not a FOO file. “  If a file can not be found, an appropriate message should be displayed, etc…

For this reason, low-level code which handles resources should report errors with as much structured information as feasible to higher-level code.  The higher-level code can then query that structure and produce an error message which is appropriate for the user.  For example, an SQL error at the lower level might mean different things; Typically duplicate a record might a User Error, but a syntax error is likely Low-Level Programmer Error.

There is far less that can be done to reduce resource errors, as they are often caused by external circumstances.  Often times using redundancy can reduce the frequency of fatal errors, but the errors themselves will still occur, and should still be handled appropriately.

Assembly line: Separation of concerns with Inversion of Control.

Friday, February 1st, 2008

If you decide to go the route of using Inversion of Control and Dependency Injection, it can make your program a lot more flexible. If you do this manually, you’ll find that even a complex system can be managed well this way.

A common idiom is to use Dependency Injection to separate the creation of the object from the use of the object. The client object has some way to receive other objects it needs to “talk” to, but the client object doesn’t care how they were created:

class MyClient {
   MyFooService fooService;
   MyBarService barService;
   public MyClient(MyFooService fooService, MyBarService barService) {
      this.fooService = fooService;
      this.barService = barService;
   }

   public void doStuff() {
      fooService.iLikeFooFighters();
      barService.buyRoundsForEveryone();
   }
}

From this code snippet, FooService and BarService could be interfaces, abstract classes, or even concrete classes. If you change your mind later about them, MyClient doesn’t have to change at all

Another common approach for DI is to use setter based injection. This approach is useful for complex object graphs that have cyclic dependencies. I’ll leave it as an exercise for the reader to re-write MyClient with setters.

So, in this phase one, you’ve separated the concern of object creation and wiring from object usage. It is perfectly acceptable to stop here. However, depending on how complicated your object system is, you may wish to separate the concerns even further. You can do this via the Builder pattern (nb. The current Wikipedia entry on Builder pattern is incorrect and misleading.)

With the builder pattern, you separate the concern of creation from the concern of “wiring”. The builder accepts a series of objects that belong in a graph. The builder knows how to connect the objects through DI, but doesn’t care where the objects came from or how they were created/configured.

In a recent project of mine, I had to create a Robot instance, and wire it with a lot of components. To make things worse, some of the components also needed sub-components, and some components needed to know about other components, and some needed to know about the robot itself. I had successfully created a class that would create the components and wire them together, but it was a bit fragile (do things in the wrong order and you get an NPE, since not all of the components were created yet).

To solve this design issue, I created a class (HardwareContext), which held a reference to every component that it takes to build a robot. Those fields were populated using setter based dependency injection. Once those references are all created, hardwareContext.wireRobot() is called, and that is where all of the object graph is connected. This would allow me to create a new hardware context that wired the components differently if I needed to. It also allows me to create a new HardwareSpecification (the class that creates components), which creates different configurations for the robot.

This leads to a highly extensible and configurable system. It also makes it easy to figure out where to add a line of code. If the line is doing wiring, it goes into the Context, if it is creating an object, it belongs in the Spec. Easy, clean, clear.

The Art of Decoupling.

Monday, December 3rd, 2007

Some people think the power of OO design is that the classes represent real-life concepts that are easier for a human to understand. Others think that its power comes from code reuse. Still other just accept OO as the paradigm that they are supposed to use, simply because they were told so.

The truth is that using any of those approaches to design may well leave you with a brittle, unmanageable, ugly mess. Personally, I strive for the “real-life concepts” as one top-level goal. The other top level goal is decoupling. Transcending all goals, of course, is creating a “correct” program. Not necessarily “correct” in the academic sense. I don’t verify every algorithm I use, and I don’t necessarily determine valid pre/post conditions. I mean correct in as much as, it works for what I need it to work for.

For small programs, this is pretty easy. When you get to larger systems, this becomes a slightly different problem to solve. In the days of goto, spaghetti code arose due to poor organization and ad hoc control transfer. Structured programming helped some by organizing the control codes into a set of well-defined idioms. Object oriented programming helped further by organizing responsibility into encapsulated modules. All of these paradigms had something in common; you could write brittle code or malleable code in any one of them. Granted, writing flexible code has gotten easier with Object Oriented programming, but only if you know what makes code brittle.

Over dependence of one section of code on another section of code. In software engineering, we call this coupling. If the behavior of A is dependent on the behavior of B, then A is dependent on B. Changing the behavior of B could change the behavior of A. This isn’t always a bad thing, but if A and B are written by different people (or the same person at different times) with different goals, it could be catastrophic. It isn’t always feasible to decouple two classes, or two methods, or even two lines of code, but if you can do it easily, consider what might be gained by that.

One syndrome I’ve noticed is that with any concept, some people take it to the extreme. For those readers who stopped at the above paragraph and decided I was right, and everything should be as decoupled as possible, you might see a design that I would call disintegrated. It is possible to create code that is so decoupled as to be impossible to figure out how one thing affects another, even though the overall system is design for A to affect B. Don’t do this :-)

One approach to decoupling two classes is to have them communicate through an event system. This will couple them both to the event system, but not to eachother. This approach allows you to replace one of the pair without changing the other side at all. This makes a lot of sense for GUI applications, for example, where user interactions with the component (such as a button) generates an event that can be handled very differently, depending on the needs of the program. Most object oriented languages, and Java in particular, already has a useful mechanism for invoking behavior on other objects. Its called method invocation, the act of making a method call. Event systems are useful for decoupling the method call from the class that needs to know about the call, its not so useful as a replacement for normal method invocation.

There are plenty of other ways to decouple you code (often through use of patterns), but I won’t get into them here. GIYF

Software design is about making difficult choices. If you didn’t have to make choices, then it would be easy to create a simple program that can design software. Your job as an engineer is to decide what should be decoupled from what. Decoupling can be a great tool to create a reusable component, but not everything should be designed to be reusable. If you’ve written something that was coupled, but needs to be made reusable… Well, thats what refactoring is for. Decoupling two classes that only communicate with each other can actually add complexity for that use case, so unless you strongly believe that at least one of the two classes will be useful in other situations, it is a waste of cognitive power to separate them.

My goal for production-quality software design? Create the simplest design that meets all my requirements, but is flexible enough to adapt to future requirements.