Tuesday, September 29, 2009

False promises

I do not like ads because the way it is made and presented makes me think that the advertisement tells me what product cannot do instead of what it can do (too many promises sound like no promises at all).

As a developer you advertise your component’s capabilities through its API. Unfortunately it so happens that we (developers) sometimes are no better than those “can do everything” magic product advertisers.

Consider the following public API (it is abstract but enough to illustrate the point):

class Container
{
    public void Put<T>(T item, string name);
    public T Get<T>(string name);
}

Let’s look at it assuming that the author had “Self-Documenting Object Models” principle in mind. What are your expectations? I would made assumptions like:

  • it is a container for named of items of arbitrary type
  • it won’t cause unnecessary boxing/unboxing for items of value types as both methods are generic
  • two items with the same name but of different type won’t overlap or otherwise want kind of behavior should I expect in cases like (I used explicit type parameter specification for illustration purposes)
    Container c = ...; // initialized elsewhere
    c.Put<int>(1, "one");
    var d = c.Get<string>("one");
    

And now let’s look at the implementation (I’ve seen similar things several times):

class Container
{
    Dictionary<string, object> items = new Dictionary<string, object>();

    public void Put<T>(T item, string name)
    {
        items[name] = item;
    }

    public T Get<T>(string name)
    {
        return (T)items[name];
    }
}

How many of our expectations are met? Only one – it is a container after all. It seems that public API advertised nonexistent capabilities. It turns out the author had different set of assumptions:

  • name is the only identifier of an item
  • generic methods allow to avoid additional casts

Wow! That is absolutely different story. Instead of advertised capabilities I got questionable advantage of not having to explicitly cast (is it really an advantage in this case?):

Container c = ...; // initialized elsewhere
var one = c.Get<int>("one");
// compared to
var one = (int)c.Get("one");

An API must be minimal and sufficient to fulfill its scenarios and clearly communicate type’s capabilities. Container for named items treated as objects may look like this:

class Container
{
    Dictionary<string, object> items = new Dictionary<string, object>();

    public void Put(object item, string name)
    {
        items[name] = item;
    }

    public object Get(string name)
    {
        return items[name];
    }
}

It doesn’t meet my original expectations but at least it makes no false promises which may lead to confusion.

Summary:

  • DO carefully consider usage scenarios
  • DO provide minimal and sufficient API that clearly communicates type’s capabilities

No comments: