Simple KISSes and surprises

Wow. Elliotte's criticism to the so called "Humane Interface" API design "school of thought" has spawn an Internet wide discussion (meme?) about simplicity in API design.

So I decided to review yesterday's entry, and go take a look at the Internet, to try to see what makes an API a good API. At least for me. And this is what I've found (and, as always, all feedback is welcome).

Surprise, surprise...

Yesterday I talked about the importance of following idioms and language conventions. Today a principle backs up this idea. The so called Principle of minimum surprise (or "Principle of least astonishment") states it clearly:

In user interface design, programming language design, and ergonomics, the principle (or rule) of least astonishment (or surprise) states that, when two elements of an interface conflict or are ambiguous, the behaviour should be that which will least surprise the human user or programmer at the time the conflict arises, because the least surprising behavior will usually be the correct one.
Principle of minimum surprise

And the fact is that Ruby's array API is all but "least astonishing" to a Java programmer. I couldn't stop laughing while reading Elliotte's review of Ruby's array api!!. (And, well, all respect due to Ruby programmers, but please understand that Elliotte has a point here, and he's so funny I couldn't resist).

Another example is C++ operator overloading. If you wrongly overload the assignation operator then your implementation fails to follow the principle of minimum surprise, and you can end up with a class that makes memory leaks. That's one of the reasons why I wouldn't like to see operator overloading in Java! ;-)

So, to summarize, point one is that an API should follow the minimum surprise principle.

Minimal interfaces: what is reasonable?

Martin Fowler introduces the Minimal Interface style of API design. He defines "minimal" as the smallest reasonable set of methods that will do the job.

And I think that's a nice definition. "smallest" and "reasonable" balance each other quite well and give the definition the part of ambiguity it needs.

Take, for instance, lists. The list abstract data type requires just one constructor and four operations. Just that and you can do whatever operation you want with lists (including "first()" and "last()"). So the "smallest set of methods that will do the job" for a list is four (and a constructor).

Well, of course a list interface with just 4+1 methods would result spartan to everybody. That's where "reasonable" fits in nicely in the definition (making the definition subjective and, thus, unusable, by the way ;-) ).

So, what is reasonable? Can we improve Martin's definition a little bit? What are the forces, the non functional requirements a Minimal Interface must meet so as to be a "Minimal Interface" with a smallest set of "reasonable" operations?

Reasonable means "as usual"

This is, a reasonable interface (to me) is one that follows the minimum surprise principle. For a Java list a reasonable interface (to me) is one that uses "size()" to get the number of elements, and that uses indexes starting from 0. And that uses Iterators to iterate over elements of the list. That's the principle of minimum surprise applied to (Java) lists. At least for me.

But a reasonable interface is one that keeps backwards compatibility. What would happen if Sun decided to remove the "Enumeration" interface in Java API? Wouldn't that break lots of existing programs out there?

Reasonable means easy to implement and extend

I wouldn't like to extend a list interface with 78 methods. That's nightmare. As Martin states it a minimal interface "reduces the burden on implementers". (Well, at least for languages such as Java, where non abstract classes implementing an interface have to implement \*all\* methods of the interface).

Reasonable means easy to learn

And again Martin introduces the importance of the learning curve in API design. Note, as well, that the more an API follows the Minimum Surprise Principle the easier the API is to learn. I don't need to go take a look at the java.util.Map API to know that I can retrieve the size of the map by invoking "size()" on it. (and yes, I admit Java API has evolved a lot and we still suffer from things such as length(), getLength() and size() burden in Java APIs).

Reasonable means easy to test

That's another important point, of course. APIs are to be maintained, and the smaller the public API the less things to test, right?

Reasonable means encapsulated, modular, providing information hiding

Which are basic principles to follow, aren't they? As Elliotte points out, is a Ruby array ...

... Is it a List? Is it a stack? Is it a map? It's three, three, three data structures in one!

And, please, note that I wouldn't like to make any critics to Ruby people, but I think the example servers quite well as a counter-example of the encapsulation and modularity. Wouldn't it be better to have separate Map and Stack classes? Is that reasonable? I think so. So, for me, a reasonable minimum interface is one that follows the principles of encapsulation, modularity and information hiding. The ones that make an array an array, and not a map, a ternary search trie or the mother of all data structures ;-)

Reasonable means ... minimal!!

APIs don't usually change. Changing a public API is asking for trouble to all people using it (remember those deprecated classes we have in APIs?). It's probably easier to augment a public API than to reduce it. Adding some new methods to a public API is much easier than removing methods people is already using. And that's why a reasonable API is a small one. The smaller the less prone to be reduced in the future, right?

To summarize

So, to summarize, I like minimal interfaces too. "Reasonable" minimal interfaces. They're easier to use, easier to learn and easier to maintain. Minimal interfaces can be augmented without too much hassle. But minimal interfaces are probably harder to design. You cannot measure the qualities of an API until you release it out. And then it may be too late to change it to make it even better. All we can probably do is use some good old common sense.

Sorry for this long post. And happy API desigining,
Antonio

Comentarios:

Amen to that, KISS is what it should be!

Enviado por Jeffrey Olson en diciembre 08, 2005 a las 01:24 PM CET #

Well... with Ruby (or any language that supports mixins or mixin-style constructs), you don't need huuuuge interfaces. You can keep your base interface (List, for instance) simple and restrict it to size(), get(), etc. With Mixins you simply add methods to a class (or even object) without changing the classes code.

For instance, many classes support iteration methods like .collect, .each, .map, ... These aren't contained in each class, but are instead inserted via a mixin. This mixin only requires that the class it's inserted into supports (from memory) methods like get() and size().
What these methods do is to use the minimal interface because that's all they need... and they don't add anything to the object layout, they simply call the objects get and size methods.

Think what this means...
If you want the same thing in Java, for instance for the new foreach-loop. The for loop will happily loop over every object that implements the Iterable interface. The problem: the person that wrote the class needs to have thought of that, and then needs to implement a way to actually iterate (either return the Iterator from a contained class or write an Iterator).

Sure... you could write a utility method like Collections.makeIterable(List) ... but then you add up with a huge Collections class ... ... wait... aren't big classes a code smell?

Another language that supports mixins is Sun's very own Fortress (I think they're called Traits there).

Enviado por murphee en diciembre 08, 2005 a las 03:57 PM CET #

For Java, AspectJ offers a choice similar to mixin using static crosscutting. I have blogged about Creating humane interfaces using AspectJ.

Enviado por Ramnivas Laddad en diciembre 09, 2005 a las 02:47 PM CET #

Quoting Elliotte:

"array.at(index) -> obj or nil

As near as I can figure, this is exactly the same as using array brackets except it uses a method call. If there is a difference, it's pretty subtle. DRY, anyone?"

The Ruby API says:

Array#at is slightly faster than Array#[], as it does not accept ranges and so on.

What's so 'subtle' about that? For me, it's pretty clear what the difference is.

Enviado por Vamsee en diciembre 16, 2005 a las 05:53 PM CET #

Enviar un comentario:
Los comentarios han sido deshabilitados.
About

swinger

Search

Archives
« abril 2014
lunmarmiéjueviesábdom
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Hoy