jacob navia said:
http://cacm.acm.org/magazines/2009/5/24646-api-design-matters/fulltext
Communications of the ACM rarely bring something practical.
This is a good exception.
It is a good article about API design, and the problems of bad APIs.
yeah, it is difficult to get API design down well...
usually I just make use of lots of "rules of thumb" I have learned from
prior experiences, and will often find an API which works well, and attempt
to use it as a template.
granted, some things are better, and other things are not as good.
for example, at a time I believed strongly in strict opaqueness, and also
really liked the design of OpenGL.
I then used it as the basis of the design of a physics library, and I
suspect that over the past several years, this opaqueness-centric API design
has probably done more harm than good.
for example, I had found that, in the choice of handles, it really does not
make much practical difference if the handles are integers or opaque
pointers, and infact the opaque-pointer practice may be better in the
general case, mostly since for any integer handle, one needs to know its
contexts, wheras pointers can be, inherently, self-identifying.
so, for an API based on integers, one may have to first "bind" a context,
and then "bind" an object, and then be able to perform manipulations. this
then makes a further issue, as then one needs to use a thread-local
variable, ... to keep track of these bindings, ...
OTOH, with a pointer, one would only need the pointer and the API call.
at the time, I had thought, "well, a pointer would be a problem if, say, a
network or disjoint address spaces is involved". though this would seem to
make intuitive sense (since an integer can be passed as-is, a pointer is
inherrently address-space specific, ...), I have found that in practice,
this is not such an issue:
marshalling opaque pointers is really not all that big of an issue, so long
as they don't necessarily need to be addressable. in fact, the "pointer" can
simply address an otherwise inaccessible region of memory, and used
essentially as an integer handle internally, ...
but, the gain of the pointer is that the "value" is inherently unique (even
if not the same in the multi-node case...), which in many cases buys far
more usability than it costs (the API can "just know" what context owns a
given object, and so the context need not be supplied).
this is not to say that integer handles are bad and pointers are good, only
that such a subtle issue can be hard to know up-front, and some bad choices
can lead to an API where using it is a lot more painful than it actually
need be.
likewise, shared structs are not quite as evil as I had once believed,
although granted, a shared struct is still something difficult to get right
(and, in general, I am still more a fan of getters/setters than I am of
having direct access to fields, ...).
however, if the struct itself is standardized, and more serves simply as a
way of moving data, it may well still be a better option than API calls with
piles of arguments, or long convoluted chains of getter and setter calls
(and having to worry about some particular getters/setters themselves either
involving expensive computations or otherwise changing state in ways which
impact subsequent calls, ...).
or, potentially, the use of adjustable unit conversions (which just happen
to be part of the global state), ...
so, it can be hard to get decisions right in retrospect even for ones' own
use cases sometimes, much less for the general case...
but I have made an observation: often the more effort I put into "designing"
something, the more likely it is to have some notable design flaw later on.
hence, in general, I have found it often works better to try to find a
design that already works well enough, and attempt to "adapt" the design to
work in the new context, than it is to design something clean (and have it
far more likely to turn out badly in the end...). (likewise, simpler is
often better, so better to design for the simple cases first, ...).
although, rigid adherence to a particular set of methodologies for an
otherwise unrelated piece of technology seems to not turn out so well
either, for example, OpenGL is OpenGL, but OpenGL is not necessarily the
perfect design template for things like Physics, GUI code, OO facilities,
....
even though the names and conventions may all look almost the same (one
creating objects and completing tasks via Begin and End pairs, ...), the
more subtle issue, that the task is inherently very different and not
necessarily a good fit for this API design style, may well impede subsequent
usability... (and, it may well make sense to have the "objects", of all
things, be the focus of attention...).
it is almost no better than those API designs where the designer sees a
problem and declares to themselves "it is all a simple matter of organizing
the class heirarchy" (as much as I despise this approach as well). "it is
all abstract properties, getters, and setters" may well not be much
better...
there are no silver bullets it seems...
or such...