Reduce Business-Object Flexibility

Posted: September 13, 2009 in .NET, Architecture, C#, Performance, Quality
Tags: , , , ,

Some developers pride themselves on writing flexible classes, making as few assumptions as possible about how other objects would use them. This sounds good, but is there such a thing as excessive flexibility?

“Excessive flexibility” implies that a class fails to do enough decision-making or encapsulation on its own, so you have to do it in the calling code. In an effort to provide a flexible class, a developer unintentionally provides a class that requires more work to use and allows less room for performance optimization.

I encounter business classes similar to the one below. This class opens a text file and provides a List<string> where each item in the list represents one line of text in the file.

public class SomeDataClass: IDisposable
{
string path;
StreamReader streamReader;

public SomeDataClass()
{
}

public SomeDataClass(string path)
{
Initialize(path);
}

public void Initialize(string path)
{
this.path = path;
}

public void Open()
{
if (streamReader != null)
streamReader.Close();

streamReader = new StreamReader(path);
}

public void Close()
{
streamReader.Close();
streamReader = null;
}

public void Dispose()
{
if (streamReader != null)
{
streamReader.Close();
streamReader = null;
}
}

public List<string> GetLineData()
{
var newList = new List<string>();

while (!streamReader.EndOfStream)
{
newList.Add(streamReader.ReadLine());
}

return newList;
}
}

The public API includes a constructor, Initialize, Open, Close, Dispose, and GetLineData. This class provides some flexibility features:

  1. You can create one using a convenient parameterless constructor.
  2. You can use the constructor overload that automatically initializes the object.
  3. You can open the file only when ready to read it.
  4. You can close and then reopen the file without creating a new class instance.
  5. You can call Dispose instead of Close if you prefer.

I would have some questions for a developer who wrote that code:

  1. Will you provide documentation on best practices when using this flexible class?
  2. Why does it need the complexity of two constructors instead of one?
  3. Does the object provide a useful service when constructed by not initialized?
  4. Does the object provide a useful service when initialized, but not opened?
  5. If Close and Dispose do the same thing, why have both?
  6. Could dispose-and-new-again replace the close-and-then-reopen functionality?

I simplified this class:

public class SomeDataClassSimplified
{
string path;

public SomeDataClassSimplified(string path)
{
this.path = path;
}

public List<string> GetLineData()
{
var newList = new List<string>();

var streamReader = new StreamReader(path);

while (!streamReader.EndOfStream)
{
newList.Add(streamReader.ReadLine());
}

streamReader.Close();

return newList;
}
}

This version provides the same functionality with only two methods. Does this class require documentation? Notice that instead of creating the StreamReader in the constructor, Initialize, or Open, it creates it only as needed. This optimization results from having more implementation flexibility inside the class whereas the first version provided more flexibility outside the class.

While I contrived this over-simplified example, the same concepts apply to large business objects.

I worked with an architect who likes the term “appropriate.” In the case of business-object flexibility, I am looking for “appropriate flexibility” in the public interface, which in most cases means, “as simple as appropriate.”

I offer a few guidelines when writing business objects:

  1. Avoid overloading constructors. A constructor should require those parameters it needs in order to reach a usable state to avoid the need for Initialize types of public methods.
  2. Avoid providing developers convenience methods that add no real value. In the first example above, just as in the StreamReader class written by Microsoft, the class provides two ways to do the same thing: Close and Dispose. A developer trying to use your class will need to read documentation to figure out how to use your class.
  3. Reduce the public interface to your class in order to provide more “wiggle room” inside the class for optimization.
  4. Write self-documenting public interfaces. The first example does not make clear the difference between the constructor, Initialize, and Open. The second example self-documents.
  5. Carefully consider the number of object states you allow. The first example has at least four valid states: (1) constructed, but not initialized, (2) constructed and initialized, but not opened, (3) constructed and initialized and opened, and (4) constructed and initialized, but closed/disposed. The second example has two states: (1) constructed and (2) disposed.
  6. Avoid exposing invalid object states. In the first example, constructing an object and then calling GetLineData would fail.  Similarly, closing the object and then calling GetLineData would fail. In complex applications, the more you expose opportunities to use classes incorrectly, the more often it will happen.
  7. If you do not immediately need a business object, do not construct it. Wait until you need it to consume the memory. Generally, dispose it as soon as you no longer need it to free resources. Write code that allows business object references to remain null most of the time.

Please add comments to refine these ideas for appropriate business-object flexibility or to show your support for these ideas.

Good luck,

Dale

Technorati Tags: ,
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s