Archive

Posts Tagged ‘java api’

Convenient or Minimal API?

December 4th, 2008 1 comment

There is always a tension in API design between convenience and minimality. I’ve found this especially true when defining interfaces.

What’s Right

As Scott Meyers has written, pretty convincingly I think, it is preferable to keep an interface minimal and then provide a library of non-member helper functions that provide common operations. A classic example of this approach is Java’s Collections class. It provides a bunch of useful methods for working with the collections framework while not bloating up the collections interfaces.

The primary benefit of this approach is improved encapsulation. When you think of encapsulation as minimizing the amount of code affected by a change in the implementation of the class, this makes perfect sense. C++’s std::string (and Java’s for that matter) is the mother of violations of this rule of thumb. It has a ton of methods, many of which can be trivially implemented in terms of other methods. Thus, any change to the internal implementation could require modifications to much more code than necessary. More code changes means greater bug potential.

What’s Feels Good

On the other hand, you want to provide a convenient interface as well. I don’t think it’s unreasonable, especially in the presence of code completion tools, to expect to hit “dot” and get a list of the operations you can perform on that type. That is, without having to know about some other set of utility functions. This is how common functions get reinvented over and over.

It’s interesting that this hang up seems less prominent in the world of dynamic languages. In Ruby, coders have no issues with adding new methods to a class, even at run-time. For statically typed languages, I think that C# might have the right idea with Extension Methods. It’s basically syntactic sugar for static helper methods that makes them look like normal object methods. As usual, Lisp (CLOS, actually) seems to really have it right. ALL methods are “static helpers”, so called generic functions. Note that I realize there’s a major distinction here! Generic functions are polymorphic on the types of their parameters, while a normal static helper is not. But if you tip your head and squint, I think the there’s a resemblance.

Why I Worry

Anyway, I’m working in Java mostly, which has proven to be much less sprightly than C# lately. Way it goes. I have to make this decision. It’s particularly annoying to have to make this decision when the API is a Java interface. In this case, implementers have to implement the convenience function, which means a chance of getting it wrong if the interface is implemented frequently, or at least changing the semantics slightly.

The example that got me thinking of all of this is the InputOutput interface from jsoar. It looks something like this:

public interface InputOutput
{
    // ...

    Wme addInputWme(Identifier id, Symbol attr, Symbol value);
    Wme removeInputWme(Wme wme);
    Wme updateInputWme(Wme wme, Symbol newValue);

    // ...
}

The tricky part is that updateInputWme method. A Soar WME is immutable, i.e. it can’t be changed after it’s been created. Thus, to update the value of WME (say to change the x coordinate of a simulation object), you have to remove the WME and replace it with a new one with the value changed. That’s what updateInputWme does, and thus it’s implemented completely in terms of the public interface:

public Wme updateInputWme(Wme wme, Symbol newValue)
{
    removeInputWme(wme);
    return addInputWme(wme.getId(), wme.getAttribute(), newValue);
}

This is such a basic operation that people expect, I decided not to make it a helper… but I had to think about it for a minute, which is pretty annoying.

When Right Is Wrong

There’s (at least) one case I can think of where you can’t use static helper methods. This is when dealing with concurrency using the private lock idiom. If the helper performs several operations and it would violate the helper’s contract for other code to see the object in the intermediate states created by those operations, the helper must be implemented as a normal method, holding the private lock as necessary.

An example of such a method is ConcurrentHashMap.putIfAbsent() from the Java concurrency library. If it was implemented naively with get() and put(), it’s thread-safety guarantees would be violated.

In cases like this, you have to just suck it up and pile the methods into the interface and worry about Scott Meyers later.

Categories: software engineering Tags: