v1.1 March 22, 2006
v1.0 December 6, 2005
Introduction
Recently I have been involved with some discussions concerned with signaling errors. Since I work in C++ style languages I was aware of two major camps, one being exception handling and the other being error return values. I discovered there was another popular camp that seems to have come from Smalltalk users called the NullObject.
Basically the NullObject was described as an object with no behavior. The premise is thus: When a method is supposed to return an object with which the caller will collaborate and the generation of the collaborator resulted in an error, instead of throwing an exception or returning an error value, return a NullObject. The NullObject will have the same interface as a non-NullObject and each method will have no behavior and no side effects.
My research leads me to understand that the NullObject Pattern is one of the Meaningless Behavior Patterns. The paper "The CHECKS Pattern Language of Information Integrity, 1994" by Ward Cunningham describes the Meaningless Behavior Pattern and two other related patterns, the Whole Value and Exceptional Value Patterns. This paper draws heavily from and often paraphrases Cunningham's work. I suggest reading Cunningham's paper. Briefly stated Cunningham is concerned with user inputs and separating good input from bad in a way that doesn't result in complicated code.
My paper does not introduce new concepts but instead is a report of existing concepts from my current perspective.
Null Object Pattern
The Null Object Pattern is not a replacement for exception handling or returning error values from functions. As with any object the NullObject is concerned with a particular set of behaviors. If you are modeling something that has various run-time behaviors then the Strategy Pattern is often used. If one of the valid behaviors for the object is "do nothing" then the do nothing strategy for this object has a special name, the NullObject. There is no rule that says you have to use the strategy pattern to arrive at the NullObject. You could implement an interface in two ways, one way being the "do nothing" or NullObject and the other way being the "do something" behavior.
When considering the run-time behavior of an object you should include this question: "Is doing nothing a valid behavior?". By asking yourself this question during design you are identifying correct behavior and exceptional conditions. By identifying more fully all of the run-time behaviors of an object you can properly identify the set of exceptional conditions. This can result in fewer errors to be signaled but it is not a replacement for an error signaling mechanism.
Whole Value
The C++ like programming languages (C++, Java, C#) provide the ability to use literal values, value types, types defined in a standard library, and the ability to create your own types that are meaningful in the domain you are working. When deciding the type of object you will use to hold your domain data you should use a type that will be complete and have the whole/entire set of information and operations needed and have a name that is meaningful.
Remember, Cunningham is concerned with capturing data at the user interface and taking that data into the domain logic (business logic) of the system. By acquiring data that is correct at the UI the business logic can be simpler, easier to read, and more intent revealing if it is not cluttered with checks for bad data.
During the design of the business logic Cunningham points out that it is tempting to select the object types from the simplest or most fundamental types available. If my business logic uses customer names it is tempting to represent the name with a String. If my business logic uses current balance it is tempting to represent this value with a float or a decimal multiplied by the literal value 10. The value is the business information but don't forget the type of the value is business information as well. If, while reading some code, you come across a variable of type String can you say what the value is other than a string? If you define a domain type of UserName then you know if you come across a variable of type UserName that it is a user's name in the context of the system.
I recently worked with a system that had two database tables for storing user information. Both of these tables used a unique key field. The older table used a long value, the newer table a string value. While studying the code I became confused when I saw a method that like this:
void DoSomethingForUser(string id)
I assumed that the method worked with the newer table. The assumption came from the input parameter being a string. Down in the implementation of the method the string was converted a long value and was used with the older table.
Consider these two methods:
int GetUserId(string customerName)
string GetUserId(int userId )
The return value doesn't express what type of user Id it is returning. It could be an 'old' Id or a 'new' Id for either method. A Whole Value would be this:
DurangoUserId GetUserId(CustomerName)
WarpUserId GetUserId(DurangoUserId )
Using Whole Values I can read the methods and understand what they do. The old system is code named Durango. The new system is code named Warp. One method takes a customer name and returns a Durango user Id. The other method converts a Durango user Id into a Warp user Id.
Cunningham warns that you should not extend your Whole Values to include or accept unexpected or exceptional values.
Exceptional Value
Trying to anticipate all of the values a user might enter into an input field in a UI is not feasible. The designer must come up with the set of acceptable values or valid domain values that are the Whole Values. Also the designer must plan for the unexpected. Cunningham suggests specifying one or more types to represent the exceptional values.
These types either return another exceptional value type or reject any method invocation. Cunningham states that it should not be necessary to explicitly test for exceptional values in methods because they will either absorb messages or produce Meaningless Behavior.
Recall that this pattern is concerned with user input. A user can input any data they want into a UI. Dealing with unexpected input is what the Exceptional Value pattern is about. Exceptional value handling is done extremely close to the UI.
When working with a UI the pattern of development is something like this:
Get user input value from UI widget
Generate domain or business object from value
Echo the domain object's value back by putting it back into the UI widget
Imagine these steps if the UI widget is to collect a date. Let's go through the steps to see how this works with an expected value and then an exceptional value.
User enters 10/7/2005
Domain object for Date is generated.
The Date object normalizes dates to be October 7, 2005. So the input widget is immediately updated with the normalized date representation upon the echo.
And now for an exceptional value:
User enters 120 Cherry Street, Lexington, Kentucky
Domain object is generated, but since this is not a date the object returned from the generator/factory is an Exceptional Value object for Dates.
The Date Exceptional Value object echoes its value back to the UI, which is nothing, so the UI date field ends up being blank.
The key to this is that the same three steps work for the valid and the invalid input. The code is clean and intent revealing. Remember, these patterns are about user input and cleaner code, and really nothing more.
Meaningless Behavior
Business rules change. If you have ever worked on an ECommerce system I ask you, "How many times have the pricing rules or product definitions changed?" Since business rules are constantly changing the developer should adhere to the advice of "writing the simplest thing that works". Trying to anticipate future changes will just create code that will forever be maintained regardless of whether or not the future changes actually followed that path. Design the business logic without concerns for possible failure and expect the UI that initiated the execution to handle possible failures. At the UI layer if there is a failure output should remain blank because any other output is an attempt to attach meaning to meaningless behavior. The user will learn that blank output is caused by invalid inputs (paraphrasing Cunningham).
Meaningless Behavior can be viewed as an alternate implementation of Exceptional Value. Use Meaningless Value if you cannot anticipate the condition as meaningful in your business domain.
Conclusion
NullObject, Exceptional Value, or Meaningless Behavior is not a replacement for signaling errors, whether the error signaling mechanism is Exceptions or Return Values.
When designing the objects in your system ask yourself if the object has a valid "does nothing" state. If so, then consider the NullObject pattern to handle that state.
When designing domain/business objects make Whole Value types to represent the data and the domain concept. Do not be overly tempted to use the most fundamental type (such as int or string) to represent domain data.
At the transition layer of user input data and domain data consider generating Exceptional Value and Meaningless Behavior types when the user's input is not expected, exceptional, or out of bounds.
Refer to Cunningham's original work on these topics.
Thursday, January 05, 2012
Subscribe to:
Posts (Atom)