cost of doing so. First, even the most
efficient wrapper adds some cost in
terms of memory and execution speed
(and wrappers are often far from efficient). Second, for a widely used API,
the wrapper will be written thousands
of times, whereas getting the API right
in the first place needs to be done only
once. Third, more often than not, the
wrapper creates its own set of problems: the .NET Select() function is
a wrapper around the underlying C
function; the .NET version first fails to
fix the poor interface of the original,
and then adds its own share of problems by omitting the return value, getting the time-out wrong, and passing
lists instead of sets. So, while creating
a wrapper can help to make bad APIs
more usable, that does not mean that
bad APIs do not matter: two wrongs
don’t make a right, and unnecessary
wrappers just lead to bloatware.
how to do Better
There are a few guidelines to use when
designing an API. These are not sure-fire ways to guarantee success, but
being aware of these guidelines and
taking them explicitly into account
during design makes it much more
likely that the result will turn out to be
usable. The list is necessarily incomplete—doing the topic justice would
require a large book. Nevertheless,
here are a few of my favorite things to
think about when creating an API.
An API must provide sufficient functionality for the caller to achieve its
task. This seems obvious: an API that
provides insufficient functionality is
not complete. As illustrated by the inability of Select() to wait more than
35 minutes, however, such insufficiency can go undetected. It pays to
go through a checklist of functionality during the design and ask, “Have I
missed anything?”
An API should be minimal, without imposing undue inconvenience on
the caller. This guideline simply says
“smaller is better.” 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. (The
C++ standard string class with its
a big problem with
aPi documentation
is that it is usually
written after the
aPi is implemented,
and often written by
the implementer.
more than 100 member functions is
an example. After many years of programming in C++, I still find myself
unable to use standard strings for anything nontrivial without consulting
the manual.) The qualification of this
guideline, without imposing undue
inconvenience on the caller, is important because it draws attention to
real-world use cases. To design an API
well, the designer must have an understanding of the environment in which
the API will be used and design to that
environment. Whether or not to provide a nonfundamental convenience
function depends on how often the
designer anticipates that function
will be needed. If the function will be
used frequently, it is worth adding; if
it is used only occasionally, the added
complexity is unlikely to be worth the
rare gain in convenience.
The Unix kernel violates this guideline with wait(), waitpid(), wait3(),
and wait4(). The wait4() function
is sufficient because it can be used
to implement the functionality of
the first three. There is also waitid(),
which could almost, but not quite, be
implemented in terms of wait4(). The
caller has to read the documentation
for all five functions in order to work
out which one to use. It would be simpler and easier for the caller to have
a single combined function instead.
This is also an example of how concerns about backward compatibility
erode APIs over time: the API accumulates crud that, eventually, does
more damage than the good it ever
did by remaining backward compatible. (And the sordid history of stumbling design remains for all the world
to see.)
APIs cannot be designed without an
understanding of their context. Consider a class that provides access to a set
of name value pairs of strings, such as
environment variables:
class NVPairs {
public string
lookup(string name);
// ...
}
The lookup method provides access to the value stored by the named
variable. Obviously, if a variable with
the given name is set, the function re-