HumaneInterface

API design

tags:

Hanging around the ruby crowd for a while, I've come across the term 'Humane Interface' quite a bit. It describes part of the rubyist attitude to writing class interfaces, I think it also sets up an interesting contrast between two schools of thought in designing APIs (the other is the MinimalInterface).

The essence of the humane interface is to find out what people want to do and design the interface so that it's really easy to do the common case.

The obvious contrast to a minimal interface is that humane interfaces tend to be much larger, and indeed humane interface designers don't worry too much about the interface being big. This isn't to say that classes with humane interfaces need be larger in terms of implementation. The fundamental functionality of the two is often quite similar.

A good way of looking at the difference between humane and minimal interfaces is to compare the list components in Java and Ruby. Java has an interface (java.util.List) which declares 25 instance methods. Ruby has an Array class (which is a list not an array) that has 78 methods. That difference in size is something of a clue of that there's a different style here (although there's more reasons for that difference). Both components offer the basic same service, but Ruby's array includes a lot of additional functionality. This functionality is all relatively small things that can be built on Java's minimal interface.

Let's take a small example to help show the difference: getting the last item on the list. To do this in Java you do:

aList.get(aList.size -1)

in Ruby you do

anArray.last

In fact it's even more startling than that: Ruby's Array has a first method too, so rather than going anArray[0] you can go anArray.first.

There's larger elements of functionality as well. Ruby's Array has a flatten method that takes nested arrays and turns them into a single level.

irb> [1,2,[3,4,[5,6],7],8].flatten
=> [1, 2, 3, 4, 5, 6, 7, 8]

The point here is all of this functionality, whether as simple as last or as complex as flatten, can be written by clients themselves without increasing the size of the list class. Minimalists tend to focus on the minimal set of necessary methods to support these behaviors, humane designers try to add methods that are needed. Often these extra methods are referred to as convenience methods, a term that minimalists do not consider to be a complement.

This begs the question: "what's the basis for deciding what should be added to a humane interface?" If you put in everything anyone might want you'll get a very complex class. Humane interface designers try to identify what are the most common uses of a class, and design the interface to make these uses easy.

Not just does this principle inspire the methods you add, it also affects how you name them. At RubyConf, Tanaka Akira pointed out the value of preferring short names for common methods. Since these are used more often you get familiar with them - it's easy to remember brief names if you use them a lot, also it's more useful since it saves typing and reading. An example of this is the parse method on DateTime that does a default parse of common date formats and the more flexible strptime that can take any format, but you use less often.

This principle of naming isn't in conflict with the minimalist approach. Indeed when Java's List interface appeared it changed the legacy Vector's elementAt method to get.

Another interesting consequence of ruby's humane interface philosophy is the aliasing method names. When you want the length of a list, should you use length or size? Some libraries use one, some the other, Ruby's Array has both, marked as aliases so that either name calls the same code. The rubyist view being that it's easier for the library to have both than to ask the users of a library to remember which one it is.

You can get long and tiresome threads about which style of interface design is best. Here I'll try to summarize the arguments in favor of the humane interface (see MinimalInterface for the other side).

Much of an object's strength lies in its behavior, not its data. If you only try to provide the minimum, you end up with multiple clients duplicating code for common cases. In cases like flatten you end up with a bunch of people writing their own recursive functions. It's not hard, but why should they bother when it's not that rare a case?

Even for simple cases like last, readers have to learn an idiom. Why should they have to see something indirect, when a simple method reads directly? Good software thinks of the users first and makes life easy for them. Humane interfaces follow that principle.

Humane interfaces do more work so that clients don't have to. In particular the human users of the API need things so that their common tasks are easy to do - both for reading and writing.

There are good arguments on both sides. Personally I lean to the Humane Interface approach, although I do think it's harder.


Follow Ups

This one caused a bit of stir, which has led to some interesting and useful discussion. At some point I might put some narrative over the links to help you read them, until then I'll just list them. The debate was mostly triggerred by Elliotte Harold's short but robust criticism of the humane approach and James Robertson's reply (make sure you check the comments on Robertson's posts). Then came the deluge | Cees de Groot | Antonio Vieiro | David Hoefler | James Higgs | Peter Williams | Cedric Beust | John D. Mitchell | Stuart Roebuck | Elliotte Harold (2) | Jon Tirsen | Hitesh Jasani | Blaine Buxton | Ramnivas Laddad | Anders Noras | James Robertson (2) | Kieth Ray | James Robertson (3) | Elliotte Harold (3) | Charles Miller | Rob Lally | Bernard Notarianni | David Crow | Jim Weirich | Jim Weirich (2) | Ian Bicking | Brian Foote | Justin Gehtland | Tom Moertel | Antonio Vieiro (2) | Kris Wehner | The Server Side | Ravi Mohan | Danny Lagrouw | Piers Cawley | Peter Williams | Florian Frank | Chris Siebenmann .

There's more too, I haven't spotted them all and I've only gone for those that I think add something interesting to the debate and avoid invective. There's been a tendency to over-focus on the Ruby Array vs Java List example rather than the underlying principles, but that's natural. There have been a number of good directions the discussion is going, if I get chance I'll try to develop one or two of them.

Or you could just read Joey deVilla - who includes excerpts from most of the above.