A site devoted to discussing techniques that promote quality and ethical practices in software development.

Sunday, May 3, 2009

Designing API - Bad API can really hurts

An article titled "API Design Matters" is a must read article for any one in charge of designing API and has this to say:
Good APIs are a joy to use. They work without friction and almost disappear from sight: the right call for a particular job is available at just the right time, can be found and memorized easily, is well documented, has an interface that is intuitive to use, and deals correctly with boundary conditions.

So, why are there so many bad APIs around? The prime reason is that, for every way to design an API correctly, there are usually dozens of ways to design it incorrectly. Simply put, it is very easy to create a bad API and rather difficult to create a good one. Even minor and quite innocent design flaws have a tendency to get magnified out of all proportion because APIs are provided once, but are called many times. If a design flaw results in awkward or inefficient code, the resulting problems show up at every point the API is called.
The article went on to provide a list of guidelines. It also offers the following top advice:
APIs should be designed from the perspective of the caller. When a programmer is given the job of creating an API, he or she is usually immediately in problem-solving mode: What data structures and algorithms do I need for the job, and what input and output parameters are necessary to get it done? It's all downhill from there: the implementer is focused on solving the problem, and the concerns of the caller are quickly forgotten.
This alternative definition requires the implementer to think about the problem in terms of the caller.
This recommendation is not dissimilar to the advice "Your users is not you" or another saying is "there are more users than developers".

The best way to deal with this as alluded in the article is to use TDD (Test Driven Design) to combat this. Let the customer, as suggested by the paper, to write the Unit Test code.

A good API reads correctly and sensibly in the user's code and not in the implementer code. Far too many 'designer' starts the API design from an implementation point of view concerning too earlier with the implementation details, which can lead to a break down in abstraction. APIs are the visible interfaces to these abstractions.

In component technologies that heavily relying on interfaces, it is important the consultants or designers begin the process on the interface.

Using UML diagram is a good starting point as you can quickly play out the scenario to see the effect. At the same time it can document the requirements (Activity/Use case diagrams), how it is to be used (sequence diagrams), or error states (State Diagrams). Then at a later stage, have executable documentation in the form of Unit Test code.

The second most import point in my mind is to strive for 'miminalist' approach:
The fewer types, functions, and parameters an API uses, the easier it is to learn, remember, and use correctly. This minimalism is important. Many APIs end up as a kitchen sink of convenience functions that can be composed of other, more fundamental functions.
This topic was given a detail treatment by Scott Meyers, "When it comes to encapsulation, sometimes less is more.". Or the YAGNI principle.

I once worked for a company that prides itself in designing kitchen-sink API, regardless if it is COM or pure C-style interface. Methods are thrown into any place it can stash regardless if they make sense. Management only interested in getting things out quickly and applying bums-on-seats resource allocation algorithm and the result speaks voluminously of the foolishness of this myopic style, for a long time.

The article touches on the issue confronting the immutability, a term not used by alluded to, which can cause "
crud that, eventually, does more damage than the good it ever did by remaining backward compatible.". When that happens, it is better to start again!

The paper correctly lays blame on education, particularly many developers are not interested or have the patience and time to learn the art and are only interested in getting something 'working' in the IDE. Experience is also overlooked when selecting someone to the task, as identified by the paper.

The example the author used to illustrate a bad API design touches on a topic that .Net/Java should have considered. The author complains about the destruction of the input parameter when using the Select() method and the API method was not dutifully documented.

In C++, you have const member function and const parameters to allow compiler to catch any misuse and to tell user of the consequence of calling the method. But this is sadly not available in .Net/Java. While you have out and ref qualifier but they serve a different purpose.

In .Net/Java, one cannot be sure what would happen to a parameter when you make a call; the method could change the state of the object, as illustrated in the example, it could take a copy of the data (cloning it), or it could hold onto the reference of the object. The latter allows the holding object to change the state of an object it has a reference to and that changes can then be reflected in some other part which has also holds a reference to this object.

All these unknowns require explicit documentation that not too many developers read and can make the API harder to use. My advice is if in doubt, use the "Learning Test" test pattern, which also has an effect of detecting changes in API's behaviour over time.

CLR does not have any support to make sure a method does not alter the state of an object; VB has a language-level construct but it is just a smoke and mirror.

On the topic of cloneable, this can be a double-edged sword. In defense of .Net, the general recommendation is not to implement ICloneable. One such problem is what if an inner class does not support ICloneable and the other is that the caller has no idea if a deep or shallow copy is used. In many way this is the flip side of C++ in which copying an object is a natural behavior.

"Test-Driven Development - by Example" by Kent Beck, Addison Wesley, 2004, Chapter 26, "Learning Test"

No comments:

Blog Archive