Thursday, August 24, 2006

Problems in Paradise

In A Not So Simple Example I laid out a sample interface definition and left you with the thought that there would be some problems with this interface.  I warn you right now this particular entry is going to be quite negative.  We want to pick on that interface unmercifully.  If you were looking for something uplifting and sprightly to motivate the start of your day, abandon hope all ye who enter here.

I want to take a paragraph or two here to talk about the motivation for defining an interface.  The reason for defining interface is to clarify the intent of the interaction between two sets of objects.  If we are one of the consuming objects, the definition of the interface should clarify the intent with respect to how to properly use an object that implements the interface.  If we are one of the implementing objects, the definition of the interface should clarify the intent of what this object should do under various circumstances.  The more comprehensive and the more precise we can make the declaration of that intent, the better off we will be.  Note that the words "comprehensive" and "precise" do not mean that we're going to force any particular implementation logic, but it does mean that we can define what the expectations are in a complete and unambiguous way. 

For example, I can define a function that computes the square root of a number.  I supply a number to the square root function and the function returns another number which when multiplied by itself is equal to the originally supplied number, plus or minus some small epsilon that represents the errors from floating-point number representation and rounding.  The function only applies to a particular range of input, typically zero through the largest number that can be represented by the computer.  If I supply a number outside of that range, the object implementing the intent of the interface will raise an argument exception.  I can write "black box" unit tests to cover each and every one of these specifications.  None of this precludes the author from selecting one or more of several different ways to compute the square root.  (Obviously, if I am the author of the function, I'm going to write some additional unit tests based upon my knowledge of the algorithm(s) that I picked.)

At this point in this entry I wanted to refer to the phrase "good fences make good neighbors" that is typically attributed to the American poet Robert Frost. The phrase does in fact come from a poem written by Frost, "Mending Wall", but is actually uttered by a neighbor of Robert Frost's. The theme of the poem was about Frost's distaste for fences. The first line of the poem is, "Something there is that doesn't love a wall”. Although the attribution of the phrase "good fences make good neighbors" to Robert Frost is technically correct, in reality, Frost was trying to say, "We don't always need fences!"  It sounds like Robert Frost would be a fan of the dynamic languages such as Ruby.  In any case, we're going to proceed as if we do need "good fences".

I'm going to list some of the problems I see.  These are in no particular order:

  • There is an open method and there is a close method.  I came infer from past experience that it is likely that the intent is that I should open a document before I perform any other operations on it and that I should close the document when I am done performing the operations.  There's nothing in the interface that explicitly says this.  There's also nothing in the interface that tells me that opening a document that is already open is either acceptable or an error.  There's nothing in the interface that tells me that closing an already closed or never opened document is an error.  I can guess the semantics here but there's an awful lot of wiggle room, and wiggle room invites Murphy to make his mischief.
  • There are a set of methods related to accessing the table of contents.  Nothing in the interface specification tells me whether or not the table of contents is a tree structure with a single root or a series of independent tree structures, one for each chapter.    From my work in this area, I can tell you that different documents have different assumptions.  Ideally, the interface would impose a single worldview so that all of the documents will exhibit a consistent behavior.  The interface should probably require that all documents have a single root. Where a specific document itself does not have this structure, the class implementing the interface for the document should make up a root.  More wiggle room and more opportunities for Murphy.
  •  The interface also does not say anything about what happens if a document does not have a table of contents at all; in my experience, there are some maintenance notices that have no table of contents but are very much a part of the documentation that the application provides to the mechanic.  Chant along with me: "Wiggle!  Murphy!  Wiggle!  Murphy!"
  • There is a method to specify the location of the document.  This method doesn't really add anything to the interface and in some cases constrains the objects that implement the interface inappropriately.  For example, it is possible that the document is in a fixed location and that the object that implements an interface for that particular document "just knows" where the document is.  There may be other instances in which it is very difficult to define the location in the form of a single string.  This information is quite variable and therefore should be outside of the interface whose purpose is to define the commonalities among all documents.
  • There is a method with the signature "bool GetNextTOC(int maximumLevel)".   The intent of the parameter of this method is to limit the depth of the table contents which is searched.  What happens if the client logic has just retrieved a table of contents entry at level 4 and the next call that the logic make to this method specifies the maximum level as one?  Does the maximum level refer to a prohibition against descending past the maximum level or does it impose a stronger restriction that says that the next table content entry must not be lower than the maximum level?  This might sound as if it is a contrived example, but it is based upon real world experience in which two different people interpreted the interface in two different ways.  Murphy rules!
  • Once the client logic positions to a particular table of contents entry, is there any requirement about the order in which the client logic can access the unique document index and title?  If I were processing this data from an XML file, and the index and title were separate elements (as opposed to attributes), it might be a lot easier if the client would access these values in the same order.  There might be errors in the logic for a given implementation of interface if the order of access were reversed.  There is nothing in the interface that tells me that I can access this data in any order and nothing that tells me that I cannot access it in any order.  As always, ambiguity is the enemy.
  • What happens if I access the "get next table of contents entry" method, get a return value of false (which says there are no more table of contents entries), and then attempt to access the ID or title values?  Do I get an exception or do I get some "empty" value?
  • The interface allows me to position to a point in the document that is specified by an input string.  What happens if the input string specifies a location that does not exist in the document?  I get back a Boolean indicator that tells me that the attempt to position failed, but what happens if the client logic ignores the indicator and attempts to access the text anyway.  Do I get an exception?  Do I get "empty" text.  Or do I get the text from the last successful positioning effort?
  • The interface specifies that I can get the text of a paragraph.  What is the format of that text?  Is it just straight ASCII characters?  What happens to the formatting that exists in the original document?

There are probably a lot more comments that I could make on this interface specification but I think the above comments should give you an idea that this interface does not completely or precisely define the intent.  If this interface is a "captive interface" meaning that there are a small number of people that will implement both sides of the interface and use the interface to enable polymorphism, this may not be a problem.  The team can build up a body of shared knowledge that extends the rather "bare bones" specification that we can write in a programming language.  But if this is a published external interface where many different people at different places and different points in time will rely upon the specification, I foresee numerous opportunities for mischief.

tags: , , , ,

Next up, some general thoughts about how to improve this and other interface specifications.


Post a Comment

Links to this post:

Create a Link

<< Home