Wednesday, August 30, 2006

Abstract Dancer

Just as I did with interfaces before evaluating whether interfaces were the way to go with the implementation of the data value objects, I thought I'd talk about abstract classes. Again, I realized that I had quite a lot to say, more than would fit into a single entry in this blog. What I thought that I would do would be to list the topics in this blog entry that I intend to talk about. Later, as I finished each of these separate blog entries, I would come back and update this blog entry to include the links to those individual blog entries. And now on with the show:

  • The Dancer Becomes the Dance. A discussion of abstract classes in general terms using dance as a metaphor.
  • You Can’t Get There from Here. An examination of the problems with abstract classes with some more use of the dance metaphor.
  • There are many paths to the Top of the Mountain. Some material on guidelines and suggestions about how to improve the use of abstract classes.

tags: , , ,

Sunday, August 27, 2006

Redemption! Oh Sweet Redemption!

In this entry I am going to comment on the revised document interface.  I have already posted the code for the interface as well as the three classes that the revised interface uses: ReturnStatus, DocumentIndex, and TOCElement.  I am including these as separate entries so that you can have multiple web pages up at the same time.  

 The documentation included in these three classes should go a long way toward helping you to understand what is going on in this revised interface.  However, there are a number of topics that I think need some additional commentary.

First, I've tried as much is possible to substitute specific classes for generic value types.  For example rather than using a string class for the document index, I've created an actual document index class.  As presented, the document index class is simply a wrapper around a string.  If you just look at the executing code, this might look a bit silly.  But if you look at the code as a way of expressing the intent, as a way of communicating to people who follow in your footsteps and look at this code in an attempt to understand it, this works quite well in my mind.  I've done the same thing with the table of contents entry.  There's a little bit more information in this class but my primary motivation for creating the class was to more clearly express the intent of the design.

Second, I've tried to reduce the number of methods and to eliminate any sequencing dependencies between the various methods.  The way that I have done this is to introduce to delegates, one to process an individual table of contents entry and a second to process a paragraph within a specific portion of the document.  There are two corresponding methods that work through the document and invoke these delegates at the corporate point.  In my experience, this is a much less error prone approach.  It does push some of the logic into the interface but the variability in how the data is stored within the individual document justifies the increased responsibility.

Third, I have created a separate class to hold the return status.  It is a personal preference, but I do not like "out" parameters.  I much prefer to have everything come back as a returned result of the function.  This eliminates much of the need for the interface to hold state between calls.  Everything that you could ever want to know, is wrapped up in the return value.  if you need more data to be returned, you can subclass the return status class to add this material.

The actual production-quality document manager interface would be more complicated.  The intent here is not to give you an implementation but rather to point out some of the ways that you can use interface more effectively.

The Revised Document Management Interface

This is the revised document management interface.  I am including this as a separate entry so that you can have multiple web pages up at the same time.  This interface uses three other class: ReturnStatus, DocumentIndex, and TOCElement.  My commentary on this revised interface will be posted shortly.  By the way, I am posting this entry using the beta version of Live Writer using the Live Writer plug-in for code formatting written by Steve Dunn.  Works quite nicely.

namespace Interfaces
{

/// <summary>
/// This is the delegate that can be used to process
/// an individual TOC entry.
/// </summary>
/// <param name="tocElement">An entry holding data from
/// the table of contents.</param>
/// <returns>An instance of the <see cref="ReturnStatus"/>
/// class that holds the success/failure indicator
/// and an error message for this invocation of the delegate;
/// if the last operation succeeded,
/// the error message is an empty string.
/// In general, the logic of the delegate should return errors
/// in the return status rather than throwing exceptions.
/// </returns>
public delegate ReturnStatus ProcessTOCElement(TOCElement toc);

/// <summary>
/// This is the delegate that can be used to process paragraphs
/// with the portion of the document that is referened by an
/// instance of the <see cref="DocumentIndex"/> class.
/// </summary>
/// <param name="documentIndex">An instance of a
/// <see cref="DocumentIndex"/> class that "points" to the
/// the portion of the document that holds the contents of
/// the <see cref="paragraph"/> parameter.
/// </param>
/// <param name="paragraph">An string holding the text of the
/// paragraph in Unicode format without any formatting
/// indicators.</param>
/// <param name="paragraphNumber">An integer that holds
/// the relative position of the paragraph within the portion
/// of the document referenced by the <see cref="documentIndex"/>
/// parameter.
/// </param>
/// <returns>An instance of the <see cref="ReturnStatus"/>
/// class that holds the success/failure indicator
/// and an error message for this invocation of the delegate;
/// if the last operation succeeded,
/// the error message is an empty string. In general,
/// the logic of the delegate should return values in the
/// return status rather than throwing exceptions.</returns>
public delegate ReturnStatus ProcessParagraph
(DocumentIndex documentIndex,
int paragraphNumber,
string paragraph);

/// <summary>
/// Defines the actions that the document interface/viewer
/// can take against a specified portion of the document.
/// </summary>
public enum DocumentActions
{
/// <summary>
/// Display the portion of the document
/// </summary>
Display,
/// <summary>
/// Print the portion of the document
/// </summary>
Print,
/// <summary>
/// Transmit a copy of the portion of the document
/// </summary>
Transmit,
/// <summary>
/// Store a copy of the portion of the document in a file
/// or other persistent store
/// </summary>
Store
}
/// <summary>
/// This is a revision of the document management interface.
/// </summary>
public interface IManageDocumentsFixed
{
/// <summary>
/// Prepare the document for use.
/// </summary>
/// <exception cref="InvalidOperationException">
/// When the client software opens an already open document.
/// </exception>
/// <returns>An instance of the <see cref="ReturnStatus"/>
/// class that holds the success/failure indicator
/// and an error message for the open operation;
/// if the open operation succeeded, the error message
/// is an empty string.</returns>
/// <remarks>The location of the document is established in
/// the constructor of the class that
/// implements this interface. It is
/// </remarks>
ReturnStatus Open();

/// <summary>
/// Retrieve the title of the document.
/// </summary>
/// <returns>The overall title of the document.</returns>
string GetTitle();

/// <summary>
/// Release the document. The document must be re-opened
/// again before any further use.
/// </summary>
/// <exception cref="InvalidOperationException">
/// When the client software closes a document that is not
/// open.
/// </exception>
/// <returns>An instance of the <see cref="ReturnStatus"/>
/// class that holds the success/failure indicator
/// and an error message for the close operation;
/// if the close operation succeeded,
/// the error message is an empty string.</returns>
ReturnStatus Close();

/// <summary>
/// Returns the reference to the "root" element
/// of the document.
/// </summary>
/// <returns>An instance of the
/// <see cref="DocumentIndex"/> class that
/// refers to the "root" element of the document.</returns>
DocumentIndex GetRootIndex();

/// <summary>
/// Run through each of the entries in the table of contents
/// in a depth-first fashion by invoking the specified
/// <see cref="ProcessTOCElement"/> delegate for each entry.
/// Each TOCElement contains the document index element
/// that can be used to position the document to a
/// specific location within the document; the title
/// of the TOC entry; and the hierarchical level of the
/// TOCElement within the table of contents.
/// </summary>
/// <param name="localRoot">An instance of a
/// <see cref="DocumentIndex"/> class that "points" to the
/// the starting point for the run through. The client
/// should use the return value from the
/// <see cref="GetRootIndex"/> method to start at the root
/// of the document.
/// </param>
/// <param name="process">A reference to a delegate that
/// conforms to the <see cref="ProcessTOCElement"/> definition.
/// </param>
/// <param name="maximumLevel">The maximum number of levels
/// to descend. The value must be non-negative. A value of
/// zero will invoke the delegate just once for the root;
/// A value of one will invoke the delegate once for the
/// root followed by once for each direct child of the root.
/// And so on.
/// </param>
/// <exception cref="ArgumentNullException">
/// When the localRoot parameter is null.</exception>
/// <exception cref="InvalidOperationException">
/// When the localRoot parameter "points at"
/// a portion of the document that does not exist.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// When the maximum level parameter is less than zero.
/// </exception>
/// <exception cref="ArgumentNullException">
/// When the process parameter is null.</exception>
/// <returns>An instance of the <see cref="ReturnStatus"/>
/// class that hold the overall success/failure indicator
/// and any error messages accumulated during the process;
/// if the operation succeeded,
/// the error message is an empty string.
/// Note that the return value is
/// the summation of all of the return values from
/// each of the invocations of the delegate.</returns>
/// <remarks>This method must be safe for recursion,
/// that is, the client program may call this method
/// to process each of the chapters. The invocation of the
/// delegate can do some process that includes a call
/// to <see cref="ProcessTOC"/> method, this time to process
/// the sections within the chapter. The logic of this
/// method must also not be affected by any use of the
/// <see cref="ProcessParagraph"/> method.</remarks>
ReturnStatus ProcessTOC(DocumentIndex localRoot,
ProcessTOCElement process,
int maximumLevel);

/// <summary>
/// Run through each of the paragraphs in the specified
/// section of the document by invoking the specified
/// <see cref="ProcessParagraph"/> delegate
/// for each paragraph.
/// </summary>
/// <param name="localRoot">An instance of a
/// <see cref="DocumentIndex"/> class that "points" to the
/// the portion of the document that holds the paragraphs
/// to be the run through. The client should use the return
/// value from the <see cref="GetRootIndex"/> method
/// to process the root of the document.
/// </param>
/// <param name="process">A reference to a delegate that
/// conforms to the <see cref="ProcessParagraph"/> definition.
/// </param>
/// <exception cref="ArgumentNullException">
/// When the localRoot parameter is null.</exception>
/// <exception cref="InvalidOperationException">
/// When the localRoot parameter "points at"
/// a portion of the document that does not exist.
/// </exception>
/// <exception cref="ArgumentNullException">
/// When the process parameter is null.</exception>
/// <returns>An instance of the <see cref="ReturnStatus"/>
/// class that hold the overall success/failure indicator
/// and any error messages accumulated during the process;
/// if the operation succeeded,
/// the error message is an empty string.
/// Note that the return value is the summation of all
/// of the return values from each of the
/// invocations of the delegate.</returns>
/// <remarks>This method is not intended to provide
/// significant display capabilities. That work should be
/// done through the viewer for the document controlled by
/// this interface. This method is intened to provide brief
/// "teaser" exerpts that can assist the user in selecting
/// a given paragraph.</remarks>
ReturnStatus ProcessSectionParagraphs(DocumentIndex localRoot,
ProcessParagraph process);

/// <summary>
/// Perform the specified action on the document at the
/// specified location within the document.
/// </summary>
/// <param name="action">A value taken from the
/// <see cref="DocumentActions"/> enumeration that
/// defines the action that the document viewer controlled
/// by this interface should take.</param>
/// <param name="documentIndex">A string value returned
/// by the <see cref="GetCurrentTOCID"/> method
/// or one of the values
/// returned by the <see cref="GetIndices"/> method.</param>
/// <exception cref="ArgOutOfRangeException">
/// When the action parameter has a value that is not found
/// in the <see cref="DocumentActions"/> enumeration.
/// </exception>
/// <exception cref="ArgumentNullException">
/// When the localRoot parameter is null.</exception>
/// <exception cref="InvalidOperationException">
/// When the localRoot parameter "points at"
/// a portion of the document that does not exist.
/// </exception>
/// <returns>An instance of the <see cref="ReturnStatus"/>
/// class that hold the overall success/failure indicator
/// and any error messages accumulated during the process;
/// if the operation succeeded,
/// the error message is an empty string.
/// Note that the return value is the summation of all
/// of the return values from each of the
/// invocations of the delegate.</returns>
ReturnStatus ActAtIndexLocation
(DocumentActions action, DocumentIndex documentIndex);

/// <summary>
/// Retrieve a set of zero or more document indices
/// that represent the locations within the document
/// where the lookup parameter value appears.
/// </summary>
/// <param name="lookup">The "index term" to search for.</param>
/// <returns>An array of document indices. If the search term
/// does not match any part of the document, the returned
/// array is zero length.</returns>
DocumentIndex[] GetIndices(string lookup);

}
}

The TOCElement Class

This class is one of several classes that I use in the revised document management interface.  I am including them as separate entries so that you can have multiple web pages up at the same time.  The revised interface and the commentary on the interface will be posted shortly.  By the way, I am posting this entry using the beta version of Live Writer using the Live Writer plug-in for code formatting written by Steve Dunn.  Works quite nicely.

Another class suitable for subclassing.

namespace Interfaces
{
/// <summary>
/// The TOCElement holds the data
/// for a single table of contents entry.
/// This includes the corresponding document index,
/// the title of the TOC entry, and the depth of the
/// TOC entry. The "root" entry is at level zero; no
/// other TOC entry can be at level zero. The Chapters
/// are at level 1, the sections are at level 2,
/// and the subsections are at level 3. Note that the
/// class is immutable; no changes can be made once the
/// class is instantiated.
/// </summary>
public class TOCElement
{
DocumentIndex _DocumentIndex;
string _Title;
int _Level;

public TOCElement(DocumentIndex docIndex,
string title, int level)
{
_DocumentIndex
= docIndex;
_Title
= title;
_Level
= level;
}

public DocumentIndex DocumentIndex
{
get {return _DocumentIndex;}
}
public string Title
{
get {return _Title;}
}
public int Level
{
get {return _Level;}
}
}
}

The DocumentIndex Class

This class is one of several classes that I use in the revised document management interface.  I am including them as separate entries so that you can have multiple web pages up at the same time.  The revised interface will be posted shortly.  By the way, I am posting this entry using the beta version of Live Writer using the Live Writer plug-in for code formatting written by Steve Dunn.  Works quite nicely.

The document index class as shown below is a wrapper around a single string.  Looked at from the point of view of reducing the number of lines of code, this looks a bit silly.  Looked at from the point of view of expressing the intent unambiguously, it is not so silly, especially when a document comes along that needs more than a simple string.

namespace Interfaces
{
/// <summary>
/// This class holds the reference to a particular portion
/// of the document.
/// </summary>
/// <remarks>
/// The intent of this class is that it can be used
/// as is for documents that can use a single string as a pointer
/// into the document or as a base class for a derived child
/// class that may use a more compilicated document indexing
/// scheme. Also, the intent is that a given
/// DocumentIndex instance be immutable once it is
/// instantiated.
/// </remarks>
public class DocumentIndex
{

private string _Index;

public DocumentIndex(string index)
{
_Index
= index;
}

public string Index
{
get {return _Index;}
}
}
}

The ReturnStatus Class

This class is one of several classes that I use in the revised document management interface.  I am including them as separate entries so that you can have multiple web pages up at the same time.  The revised interface will be posted shortly.  By the way, I am posting this entry using the beta version of Live Writer using the Live Writer plug-in for code formatting written by Steve Dunn.  Works quite nicely.

The ReturnStatus class is used to return a status.  If there are other values that should be included in the return status, you could subclass this class or you could modify this class to hold some generic (Object) values.  Of the two approaches, I prefer subclassing.

 

using System;

namespace Interfaces
{
/// <summary>
/// The ReturnStatus status class holds the status generated
/// by various operations. It is designed to make
/// the generation and manipulation of return status values
/// easy and painless.
/// </summary>
public class ReturnStatus
{

#region Variables

private string[] _Errors;

#endregion

#region Constructors

/// <summary>
/// Instantiates the class with zero or more error messages.
/// This is the prime constructor; all of the other
/// constructors come here to do the actual work.
/// </summary>
/// <param name="theMessages">An array of error messages.
/// The array can be zero length.
/// All supplied messages must be non-null and non-empty.
/// </param>
public ReturnStatus(string[] theMessages)
{
_Errors
= new string[theMessages.Length];
for(int MsgNdx = 0; MsgNdx < theMessages.Length;
MsgNdx
++)
{
CheckMessage(theMessages[MsgNdx]);
_Errors[MsgNdx
++] = theMessages[MsgNdx];
}
}
/// <summary>
/// Instantiates the class without any error messages.
/// </summary>
public ReturnStatus()
:
this(new string[0])
{
}
/// <summary>
/// Instantiates the class from a single error message.
/// </summary>
/// <param name="theMessage">
/// A single error message for the status.</param>
public ReturnStatus(string theMessage)
:
this(new string[1] {theMessage})
{
}

/// <summary>
/// Instantiates the class from a previous return result
/// plus an error message.
/// </summary>
/// <param name="thePreviousReturnStatus">
/// A previously created return status.</param>
/// <param name="theMessage">
/// An error message for the status.</param>
public ReturnStatus(ReturnStatus thePreviousReturnStatus,
string theMessage)
:
this(CombineMessages(ExtractMessages(thePreviousReturnStatus),
new string[1] {theMessage}))
{
}

/// <summary>
/// Instantiates the class from a previous return result
/// plus an array of error messages.
/// </summary>
/// <param name="thePreviousReturnStatus">
/// A previously created return status.</param>
/// <param name="theMessages">
/// An array of error messages for the status.</param>
public ReturnStatus(ReturnStatus thePreviousReturnStatus,
string[] theMessages)
:
this(CombineMessages(ExtractMessages(thePreviousReturnStatus),
theMessages))
{
}

/// <summary>
/// Instantiates the class from two previous return results.
/// </summary>
/// <param name="thePreviousReturnStatus">
/// A previously created return status.</param>
/// <param name="theNewReturnStatus">
/// Another previously created return status.</param>
public ReturnStatus(ReturnStatus thePreviousReturnStatus,
ReturnStatus theNewReturnStatus)
:
this(CombineMessages(ExtractMessages(thePreviousReturnStatus),
ExtractMessages(theNewReturnStatus)))
{
}

#endregion

#region Methods

/// <summary>
/// Return the count of the error messages within this object.
/// </summary>
/// <returns>An integer count of error messages.</returns>
public int GetErrorCount()
{
return _Errors.Length;
}
/// <summary>
/// Return the specified error message.
/// </summary>
/// <param name="MsgNdx">
/// An index to the error messages.</param>
/// <returns>If the index is in range, the error message;
/// if the index is out of range, an empty string.</returns>
/// <remarks>We could throw an ArgumentOutOfRange exception
/// here but I want to get back an empty string
/// if there are no errors, which technically is
/// an out of range condition. Having set
/// a foot on the "slippery slope" it does not seem to do
/// much damage to return empty strings in all other
/// out of range situations. </remarks>
public string GetIndexedError(int MsgNdx)
{
if(MsgNdx < 0 || MsgNdx >= _Errors.Length)
{
return "";
}
return _Errors[MsgNdx];
}
/// <summary>
/// Return the first error. This is a convienence
/// method to handle the most likely situation.
/// </summary>
/// <returns>The first error message.</returns>
public string GetError()
{
return GetIndexedError(0);
}
/// <summary>
/// Returns an indicator of whether there are errors
/// in the return result. This is a convienence method
/// to handle a common inquiry.
/// </summary>
public bool IsOK
{
get { return _Errors.Length > 0; }
}

#endregion

#region Support Methods

/// <summary>
/// Check that an error message is real and substantive.
/// </summary>
/// <param name="theMessage">
/// The string for the error message.</param>
/// <exception cref="ArgumentNullException">
/// When the message parameter is null.</exception>
/// <exception cref="ArgumentException">
/// When the message parameter is an empty string.</exception>
private static void CheckMessage(string theMessage)
{
if(theMessage == null)
{
throw new ArgumentNullException("Message",
"An error message must be supplied");
}
if(theMessage.Trim().Length == 0)
{
throw new ArgumentException("Message",
"An error message may not be empty (zero-length)");
}
return;
}
/// <summary>
/// Returns an array which holds the combined error messages
/// from the return status and an array of error messages.
/// </summary>
/// <param name="thePreviousReturnStatus">
/// A previously created return status.</param>
/// <param name="theMessages">
/// An array of error messages</param>
/// <returns>An array (possibly zero length)
/// of error messages.</returns>
private static string[] CombineMessages
(
string[] theOldMessages, string[] theNewMessages)
{
int oldCount = theOldMessages.Length;
int newCount = theNewMessages.Length;
string [] myErrors = new string[oldCount + newCount];
int MsgNdx;
for(MsgNdx = 0; MsgNdx < oldCount; MsgNdx++)
{
myErrors[MsgNdx]
= theOldMessages[MsgNdx];
}
for(MsgNdx = 0; MsgNdx < newCount; MsgNdx++)
{
myErrors[MsgNdx
+ oldCount] = theNewMessages[MsgNdx];
}
return myErrors;
}
/// <summary>
/// Build an array of error messages
/// from the specified return status.
/// </summary>
/// <param name="theStatus">
/// A previously created return status.</param>
/// <returns>An array (possibly zero length)
/// of error messages.</returns>
private static string[] ExtractMessages(ReturnStatus theStatus)
{
int count = theStatus.GetErrorCount();
string [] myErrors = new string[count];
for(int MsgNdx = 0; MsgNdx < count; MsgNdx++)
{
myErrors[MsgNdx]
= theStatus.GetIndexedError(MsgNdx);
}
return myErrors;
}

#endregion

}
}

Saturday, August 26, 2006

Yesterday

Yesterday was my last day on my last project for my current employer.  As I was driving to the airport to come home, I listened on the radio to the song "Nights in White Satin" by the Moody Blues.  The radio station played the long version which ends with the following poem:

 

Late Lament:

Breathe deep the gathering gloom

Watch lights fade from every room

Bedsitter people look back and lament

Another day's useless energy spent.

 

Impassioned lovers wrestle as one,

Lonely man cries for love and has none.

New mother picks up and suckles her son,

Senior citizens wish they were young.

 

Cold hearted orb that rules the night,

Removes the colors from our sight.

Red is grey and yellow white,

But we decide which is right.

And which is an illusion?

 

That seem to set the tone of the day.  I'm going to a new job and I am excited about that.  But I am also closing out a chapter in my life.  There are dreams that can no longer be realized.  There are expectations can no longer be fulfilled.  There are possibilities that are no longer relevant.  I am one who writes.  Much of this "coulda, shoulda, woulda" is reflected in my writing.  As I sift through each of the files on my laptop which I will turn in a week or so, I have to decide whether to delete the file, to archive the file for my old employer, or to keep a copy for myself.  I spent over half a decade here and there are more than a few things in that latter "I can't bear to throw it away just now" category.  At one point, and perhaps still now in some sense, these things were important to me.  By changing jobs, I am deciding that they will never come to be.

With any decision, there's always the nagging thought: is this the right thing to do?  Is my appraisal of the current situation valid?  To resolve this uncertainty, we focus on some of the bad things in the current situation to justify making the change.  There is a temptation to focus on the phrase "Another day's useless energy spent".  I learned a great deal in the time that I was here.  Not all of it was because of my employer's efforts.  And some of it was in spite of my employer.  In many ways I am much better prepared to do the things I want to do than I was when I started years ago.  It could have been better, perhaps much better, but it could also have been worse.  I am reminded of Nietzsche's famous comment: "That which does not destroy us utterly, makes us stronger."

I respectfully add these unfulfilled hopes to the pile of things I will keep.  In a year or two, or five, I will look at them again.  Perhaps I will throw them away then and perhaps I will not.

tags: , ,

Negotiating with a guitar string

After my last post, I Got Voted off the Software Engineering Island?, I happen to read this entry,

Test-Driven Development is Like Tuning A Guitar, in Scott Bellware's blog.  It seemed relevant so I'm mentioning it here.

Friday, August 25, 2006

I Got Voted off the Software Engineering Island?

It is very easy to be negative about interfaces. 

First, an interface demands that the implementing class provides logic for each and every method in the interface but in return provides the implementing class with no mechanisms to create the logic.  Each time that you set out to create a class that implements the interface, it is a brand-new ballgame.  The only inheritance that you have is the "cut and paste" variety.  Implementing an interface is not for the faint of heart.

Second, even when you have implemented all of the methods, if the interface changes, everything that touches the interface has to change right along with it.  Compliance with an interface is a binary thing: it is either completely implemented or not.  There is no partial compliance with an interface; no "the check's in the mail" promise to buy yourself out of trouble temporarily.  This means that if you do change an interface that has gotten out into the wild, every program that is involved with this interface must change immediately.  That is often very hard to do. 

All of this coupled with the caveats listed in "So You Think You Can Dance" might lead you to think that interfaces are more trouble than they are worth: it is time to vote them off the island. However, interfaces are like almost anything else, used correctly and in the proper proportions, and can add a great deal to the application.

My purpose in writing this entry is to cover some of the positive aspects of interfaces and offer some advice about how to use interfaces in a way that reduces the number of negative effects.

One of the major benefits of using interfaces is that they can appear anywhere.  A given class can implement dozens of interfaces (although that's reason for suspicion about that class).  Most importantly because many programming languages do not support multiple inheritance (for many good reasons), the only way to get common behavior defined in different object hierarchies is to introduce interfaces.  This is one the of the ways by which one can get polymorphic behavior across multiple object hierarchies.

Ideally, an interface is compact and sharply focused.  One of the major reasons why some developers end up putting in dummy implementations for some of the methods required by interface, is that the interface is too comprehensive; it tries to be too many things to many people.  A great interface does one and only one thing and every method in the interface is essential to support that one thing.  If you find that developers are creating dummy methods to satisfy the requirements of interface, it is time to rethink the interface to sharpen the focus and make it more compact. Because it is possible for a given class to implement multiple interfaces, there really is minimal reason to keep the interface that addresses two or more separate "topics" as a single interface.

Although I think that this next recommendation carries a little less weight than the "compact and sharply focused" recommendation, I think that is very useful to build interfaces that don't maintain state.  Because of the lack of ability to specify the semantics concerning the order in which these methods should be called, there is a real danger that different consumers of the interface and different implementers of the interface will come to different conclusions about how state is maintained.  Again because there is no reference implementation for the interface, any possibility for misinterpretation of the intent of the interface should be avoided. 

Consider the situation where the interface provide you with a method to perform some operation which may possibly fail.  The operation could return a Boolean value to indicate success or failure.  If the method failed, the consuming class could then call another method on the interface to get back the error message and possibly extensive debugging information.  This is dangerous for two reasons, first, it requires the object implementing the interface to hold state values that may never be used (there is no way to force the consuming class to call the "get error data" methods); second, there may be confusion about how long that state value is to be held.  Other calls to other methods could overwrite that state.  It is much better to create a "results object" that can hold all of the results from the function including multiple error messages, if they exist, and an indicator of the success or failure of the operation.  Each operation would return an instance of this "return status" object, thus releasing the implementing object from having to maintain that state data.

In the case of our document management system, the need to maintain state across multiple methods in the interface can be quite complex.  The sample interface that we provided in A Not so Simple Example allowed the client logic to walk through the tree structure that held the table of contents.  While this might on the surface appear to be something akin to a simple enumeration of an array, I have personal knowledge of how complex this can be.  Consider the case where the table of contents is scattered throughout the document in many different files.  Each time that the implementing object returns from a "get next table of contents entry", the implementing object has to capture a lot of data about where it was in the document.  It may even be the case that the implementing object has to retrace the steps each time that the "get next table of contents entry" method is called.  It may turn out that it's far better for both the implementing and consuming objects to allow the implementing object to loop through the data structure and call a delegate within the consuming object for each entry.  This does not complicate the consuming object logic very much, if at all, and can vastly simplify the logic within the implementing object.

I believe that "truth is only understood through argument."  What I mean by this is that one has to work through all of the issues and considerations around a particular truth before one can understand it.  The "truth" cannot be merely handed down and accepted blindly.  The development of an interface is very much the same type of thing.  High quality interfaces are typically the result of a negotiation between the consuming objects and the implementing objects.  Before the definition of an interface is released out into "the wild", the development team should have created two or three objects that implement the interface.  Doing a single implementation is probably not sufficient.  The line of demarcation between the consuming object and the implementing object is somewhat arbitrary.  One could easily add more functionality to the implementing object or move some of that functionality to the consuming object.  It is not until you have created two or three objects that implement the interface that you begin to understand where the commonalities and variabilities are.  In my experience each new class that implements interface brings new insights as to how that interface should really be defined.  Even if these are somewhat simple-minded test implementations, they provide valuable insight particularly if they are done by different people with different points of view.  There is an essential creative tension between the needs of the consuming object and the needs of the implementing object.

Designing interfaces very much like writing one of these blog entries.  It doesn't just happen, one has to think about the issues, create some form of outline, write the document, review the contents, edit and rearrange the material so that it makes more sense, and only then can one publish the entry.  It is the responsibility of the author of an interface specification to understand the fundamental needs of the consumer class as well as the realities of trying to implement an interface for different situations.  A well-defined interface balances the needs of the consuming object against the difficulties of implementation for the implementing object.  Again that balance can only be achieved by considering multiple implementations.

A final topic is unit testing of interfaces.  Those of you who have not been lulled into a coma by this time might be holding up your hands, saying, "But wait, there's nothing behind an interface, there is no implementation.  How can you test an interface when there is no implementation?"  While that is true, the value of unit testing is just too powerful to give up just because there is no implementation.  How are we going to solve this.

First, we can get around the lack of an implementation by creating an implementation.  I often create "mock" objects to assist in the unit testing.  The mock objects keep track of what parts of the interface have been called.  The unit test sets up the processing object that is to be tested with a reference to my mock object, invokes the methods on the processing object, and queries the mock object to see if things came out the way that I expected.  Writing a mock implementation of the interface gives one more experience in implementing the interface and can yield additional insights.   

Second, we can write a set of unit tests against the interface.  Each test would invoke a method to get an instance of an object that implements the interface.  The logic of the test would be oriented toward the abstract interface rather than a specific class that implemented the interface.   For example, one could write a unit test to verify that the "get title" method always returned a non-empty string.  This is a very valid unit test.  Indeed there are those who are strong advocates of unit testing that would suggest that these unit tests are the only true specification of what the interface should do in its totality.  I certainly wouldn't want to disagree with that sentiment.  What we want to do is to be able to apply these abstract unit tests to a variety of classes that implement the interface.  If we have five classes that implemented the interface, we would want to run these same tests against each of these five classes.  We could make copies of the test class, each with a different implementation of the method that returns the object implementing the interface, but that would be wrong. 

Third, we can convert the class with the abstract unit tests into an abstract base class.   Each of these abstract test cases are marked with the [Test()] attribute.  The base class as a whole is NOT marked as a [TestFixture()] attribute.  We convert the "get instance of an object that implements the interface" into  an abstract method that must be implemented in a derived child class.  The abstract method returns an instance of an object that implements the interface.  Each of the abstract test methods in the base class would apply their tests to the return from the abstract class.  Obviously these tests would have to be somewhat generic and general in their approach but would still serve as a fairly definitive statement of what the minimum requirements for each object that implemented the interface.

Fourth, we create a derived class for each different class that implements the interface.  This class would be marked as a [TestFixture()].  Each derived child class would implement "get instance of interface" method to return an instance of the object that implements the interface. Each child class could also include tests that were more specific to the particular implementation.

This form of unit testing contributes to the negotiation process that tightens up the interface definition.

tags: , , , ,

In our next couple of entries, I'm going to present a rewritten interface that reflects some of these recommendations.