
The Worst API Ever Made? - AndyKelley
http://mollyrocket.com/casey/stream_0029.html
======
vl
I claim MS Crypto API is even worse (I used both Crypto and ETW on the same
project, it's by far not as bad, good luck tuning it not to lose buffers under
load though):

I was implementing TLS/SSL in one of the services working at MS. I couldn't
figure out many things from MSDN and samples - they would not cover error and
some variations code paths, and there was just no way to figure it out. And
recovery would be something like "in third buffer there will be value x, you
have to it pass to other function". And there was a need to do it correctly
for obvious reasons, really. So finally I requested access to IIS sources to
see how it's done correctly, and discovered couple thousand lines of code with
comments like this "xxx told us that buffer X will contain value bla if
condition Z is met, and this is why we are doing it here". I had no choice but
to cut-n-paste it to my service. I can tell you for sure, nobody outside MS,
without access to internal sources? can implement TLS/SSL correctly by using
MS Crypto APIs.

~~~
klodolph
I think it has to be the sheer number of APIs that Microsoft has developed:
they just don't have the resources to care about very many of them. It stems
from Microsoft's extremely insular culture in the 1990s and 2000s, where being
a "Microsoft developer" meant that you just didn't read code for open-source
projects, didn't look at competing APIs, just did everything your own way.
Apple had its own bad APIs, and blew a bunch of them away when OS X came
along. So you want TLS/SSL on OS X, the typical way to do it was to just use
OpenSSL. OpenSSL has tons of problems, but at least they're well-understood
problems, with source code and documentation.

~~~
wfunction
> OpenSSL [...has...] documentation

Did you really praise OpenSSL for its documentation? Honestly, until now I
thought it was the de facto example of poor documentation.

~~~
vl
While OpenSSL has bad documentation - there are both source code and real-
world usage examples (from open source products using it) to help.

------
wfunction
"The API is simple because the problem it’s solving is trivial."

I beg to differ -- as ridiculous as the API interface may be, the problem it's
solving is most certainly _not_ even close to being trivial. High-performance
logging for something as low-level as the thread scheduler is _not_ something
you can write in your sleep.

~~~
balloot
When an engineer uses the word "trivial," what you should hear is "There are
some complications that I'd rather ignore, so let's just hand-wave the
answer".

~~~
to3m
What are these complications? The API's job appears to be to let you iterate
through a list of log items/read data from a log buffer/however you want to
imagine it. That sort of thing is not rocket science, no matter how difficult
it was to make that data in the first place.

(Besides, even if you don't think there's anything wrong with the way it
provides the caller with data from the list, there's always the session
nonsense to point and gawp at.)

~~~
wfunction
> That sort of thing is not rocket science

Uh, for starters, the buffer doesn't have infinite size. It _will_ overflow.
What is the system supposed to do here? There are a million possibilities
(discard old data, discard new data, allocate more memory, write to a file,
call a callback, return an error, stall the rest of the system or halt the
clock, etc.); some make sense, some don't. Between those that do, the user
needs to be able to choose the best option -- and the time-sensitive nature of
the log means you can't just do whatever pleases you; you have to make sure
you don't deadlock the system. That's _not_ by any means a trivial task, and
I'd bet the reason you think it's so easy is that you haven't actually tried
it.

~~~
__david__
> What is the system supposed to do here? There are a million possibilities…

No, there are two: You dump old data or you dump new data. Everything else
should be up to the user code. It's really not as difficult as you are making
it out to be. There's certainly no excuse for a ridiculous API as described in
the article.

~~~
wfunction
Huh? If you dump data you miss events. Imagine if Process Monitor decided to
suddenly dump half of the system calls it monitored. Wouldn't that be
ridiculous? For a general event-tracing system, there _have_ to be more
options provided. Maybe it wouldn't matter so much for context-switching per
se, but for a ton of other types of events you really need to track each and
every event.

~~~
__david__
Yes, you miss events. But if you try to make build the kitchen sink into your
low-level logging system then it ceases to be low level. If your logging
system allocates memory then how can you log events from your VM subsystem? If
your logging system logs to the disk, then how do you log ATA events? It
becomes recursive and intractable.

The solution is to make your main interface a very simple pre-allocated ring
buffer and have userspace take that and do what they please with it (as fast
as it can so things don't overflow).

There is _always_ a point at which your logging system can't keep up. At the
kernel level you decide which side of the ring buffer to drop (new data or
old) and at the userspace level you decide whether to drop things at all or
whether to grind the system to a halt with memory, disk, or network usage.

~~~
wfunction
The options are not simply "drop data" or "don't drop data". The options
depend on the logging source, because not every logging source requires a
fixed-size buffer. The API itself needs to support various logging sources and
thus needs to support extensible buffers (e.g. file-backed sources, the way
ProcMon does). Whether or not a particular logging source supports that is
independent of whether or not the generic logging interface needs to support
it.

~~~
__david__
I think we're talking past each other here. I don't think we're disagreeing on
the userspace part. I'm not even implying that the the low level kernel
interface should have unconfigurable buffer sizes. They should be
configurable, but pre-allocated and non-growable. You're right, the userspace
part can do whatever it wants. But I stand by my last paragraph (you either
drop or grind things to a halt).

------
asveikau
Poster has not worked very deeply with Win32, is unaware of its conventions.
Film at 11.

For example:

> Yes, that’s right, every user of the Event Tracing for Windows API has to do
> the arithmetic and layout of the packed structure format themselves.

These represent very common idioms in Windows. One common idiom is about
binary compatibility. Microsoft can change the length of the structure in a
future rev of the SDK. Old callers can still work because they are specifying
sizes and offsets - the library can look at these and know what to do. The
other common idiom (very common in the NT kernel for example) is similar to
what C99 introduced for structures with variable-length members, something C
definitely didn't do for you and even today with it standardized still gets
pretty clumsy.

The author lost all credibility when he wrote this:

> StringCbCopy((LPSTR)((char * )pSessionProperties +
> pSessionProperties->LoggerNameOffset), sizeof(KERNEL_LOGGER_NAME),
> KERNEL_LOGGER_NAME);

Why on earth would you say (LPSTR)(char * ) ? That is literally saying (char *
) (char * ).

To me a "bad" API enters into questions like:

* How does it handle errors? Consistency is good. Swallowing them to the caller is bad.

* Does it give the caller the right level of detail about what is going on? It's especially common for it to be a black box and completely fail under some condition that the author did not envision. Some kind of escape hook that exposes implementation details makes library maintanence difficult but sometimes it's needed.

I haven't looked _too_ deeply at etw but I don't suspect it fails at these.
Maybe it errs too much on one extreme on the 2nd bullet.

~~~
anon4
Yes, that's frequently done in win32 land, but in this case, I'd argue the
following should have been done

1) Why provide a copy of the parameter at the end of a struct?

2) How will you ever expand that struct given the fact that you put a
variably-sized member at the end?

3) Why doesn't the current version of the header come with a

    
    
        char[] SessionName;
    

member as last member, so it's at least halfway convenient?

4) No seriously - why is it copied in at the end and not a pointer?

5) That struct doesn't even have a DWORD size member as first member, are we
expecting to get coupling between some flag and "oh and expect there to be
additional members after that char array"?

Even for Win32's usual badness, it's pretty bad.

~~~
asveikau
> No seriously - why is it copied in at the end and not a pointer?

Consider where you have seen similar patterns in the Unix world. The obvious
one would be they intend to pass the buffer to kernel mode and a structure
with lots of pointers inside will be a pain in the ass to pass over and
validate.

A flat buffer with a couple of offsets works better for that. Copy over the
whole blob, check a few lengths. Generate your EFAULT errors in a single
place. Better than following lots of user mode pointers.

------
perlgeek
Another "fun" API is the one from MUMPS (matrix processing stuff) in Fortran.

See
[http://mumps.enseeiht.fr/doc/userguide_4.10.0.pdf](http://mumps.enseeiht.fr/doc/userguide_4.10.0.pdf)
, for example page 10.

It has lot of numbered "ICNTL" (input control) variables as well as named
struct fields, and some of those struct fields are only valid if, for example
ICNTL(26) has the values 0, 1 or 2.

ICNTL is actually an array with 40 elements, though "only" 1-33 are currently
in use. And then there's CNTL (float control parameters), INFOG, RINFOG and
what not.

Also, the number of calls you must do to the actual processing function
depends on some combination of those magic variables.

Needless to say that there are no named constants for all those magic
variables, because the code must be backwards compatiable with Fortran 77,
where the behavior of code with identifiers longer than 8 characters is
undefined, and differs from compiler to compiler (some truncate, some allow
them, some error out). So names are actually in short supply.

~~~
akx
Wonder what it is about the name MUMPS that makes software called that so
terrible...

------
bitroliest
I had amazing "fun" with Amazon's marketplace API. Highlights include: \-
rejection without error messages \- multiple hours before the successful call
showed up as successful \- broken XML schemas along with conflicting
versioning and

My favorite: \- error messages from the API asking me to call customer service
to perform that action.

~~~
winestock
According to Eric S. Raymond's version of the Jargon File, people used to joke
that MS-DOS system calls delivered "same-day service." Now that we're living
in the future, that joke has become literally true.

------
ahelwer
I thought the standard way to use ETW is by generating code from a manifest
file with Message Compiler. Presumably it takes care of all this boilerplate?
You're right though, that's a weird API. It'd be interesting to learn the
process behind its design.

~~~
MaulingMonkey
This is the way I've done it. Which boils the problem down to an init call and
a call per event. I dislike the code generated bindings though - is there a
more... data driven alternative that's good?

I've got a simple event API where I have an event type identifier, and some
basic data. Which I then need to pipe to some third party junk that listens to
ETW events. To do this, I have to:

1) Manually map hundreds of event types to code generated ones, and update
this mapping every time new events are added... or do a massive N:1 mapping...
which is totally against the grain of the intended usage of the third party
junk (although appears workable for the moment.)

2) Wrap every ETW event type with callbacks, and the init macro with a stub to
allow callbacks, such that I may target the two "identically" configured
endpoints... each with their own manifest and autogenerated header.

3) Wrap every ETW invoke with #ifdef s to limit them to the 1 of my 3
platforms they work on.

It's quite ugly compared to the equivalents of the other 2 platforms.

------
mwww
Can anyone recommend any resources about API design?

~~~
_pmf_
For a very deep, thorough and painful treatment instead of feel-good books,
read "Practical API Design" by Jaroslav Tulach. Yes, it's Java, but it
exemplifies the fundamental tradeoff that the feel-good books ignore: the more
powerful you make your API for users, the less potential for evolution and
long-term maintenance your API retains.

~~~
monksy
That is on my queue of books to buy. [Which is below my queue of books to
read].

So shouldn't there be a way to balance these requirements?

It seems like we've ignored the ability to batch jobs, do async tasks etc. It
sucks... but an API is out of my control.

------
pwilson_gorge
I offer a simple API decision which has condemned generations of programmers
to useless toil. The decision in .Net to not map database NULL to programming
language null. Perhaps there was some higher level philosophical distinction
being drawn which mere mortals are not capable of understanding.

~~~
Someone1234
> The decision in .Net to not map database NULL to programming language null.

What specific API in the .Net framework does this (or doesn't do this)?
Definitely hasn't been my experience but I am using Entity Framework so...

It goes so far as to mark null-able database types as (bool?) (nullable bool)
which is super helpful and you can use == null for comparisons.

~~~
Hakeashar
ADO.NET uses DBNull: [http://msdn.microsoft.com/en-
us/library/system.dbnull(v=vs.1...](http://msdn.microsoft.com/en-
us/library/system.dbnull\(v=vs.110\).aspx)

There's one valid (if obscure) use case for it, other than that it is pretty
much redundant:
[http://stackoverflow.com/q/4488727/1180426](http://stackoverflow.com/q/4488727/1180426)

All modern (micro)ORMs, even if they're just a thin wrapper over ADO.NET like
Dapper, work just fine with regular nulls.

------
erikb
If you feel the pain of public APIs you should definitely go and try some
internal APIs companies write for themselves. Somehow they can really get
worse than what people used to write for Programming 101 homeworks.

------
rossjudson
I thought this was going to be link-bait crap until I actually read through
the damn thing. My god. He could be right.

------
outworlder
I was half expecting the rant to be about TAPI. From memory, I remember at
least one instance where it returns a void* pointer, with a few integers
telling the offset of the information you need to get.

But at least the function calls made sense. This is much worse.

------
amaks
A funny anecdote on the topic. When I worked in WinFS team, folks tried to add
new flags in CreateFile API. Turns out, all 32 bits in dwFlagsAndAttributes
were already taken, including the hidden usages inside the Win32 subsystem
implementation.

------
malkia
Nothing beats up the P4 C++ Api :)

While I love perforce, their constantly changing C++ api with it's namespace
wasting, bad error handling API is the worst out there...

------
hyperliner
"ALWAYS start by writing some code as if you were a user trying to do the
thing that the API is supposed to do."

It still amazes me how many times junior devs ignore this. They will spend a
lot of time walking through the UX scenarios, mocking it up, etc. But when
designing the API layer, they just start creating end points instead of
focusing on the "DX" scenarios (developer experience, not theirs, but devs
using the API).

I remember reading Cwalina's "Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries" and really learning what
that means. It is a book focused on .NET but I wish many of my colleagues in
the open source world would get over that and just read it. It would change
their view on API design. And that is just the first chapter.

~~~
voltagex_
The second edition of the Framework Design Guidelines book was written in 2008
and seems (from a 10 minute browse of the first chapter) to be pretty up to
date still.

It's ISBN 9780321545671 if anyone's looking for it (it's on Safari Books
Online, too)

~~~
walterbell
First chapter is included in the free preview:
[http://www.amazon.com/Framework-Design-Guidelines-
Convention...](http://www.amazon.com/Framework-Design-Guidelines-Conventions-
Development-
ebook/dp/B0017SWPNO/ref=sr_1_1_title_1_kin?s=books&ie=UTF8&qid=1407386835&sr=1-1&keywords=9780321545619#reader_B0017SWPNO)

------
zvrba
As somebody who tried to use ETW, I can say that the OP didn't do his
homework. Everything you need to know about ETW is described here:
[http://msdn.microsoft.com/en-
us/library/windows/desktop/bb96...](http://msdn.microsoft.com/en-
us/library/windows/desktop/bb968803\(v=vs.85\).aspx)

ETW is much more than a simple logging system; the description says "Use ETW
when you want to instrument your application, log user or kernel events to a
log file, and consume events from a log file or in real time. ".

It allows broadcast (multiple consumers), structured binary data, filtering
based on data structure, etc. It is an immensely powerful system (See
manifest-based events above). In addition to everything it does, it is also
designed for performance and for not using too much space.

Re my attempt to use ETW: I began to drool when reading the docs; it would be
immensely useful in my project. However, the product must also work on Linux,
and building a cross-platform layer for something like ETW (even if the Linux
counterpart is a no-op) would be an overkill. I might return to it one day.

~~~
klodolph
So, "as somebody who tried to use ETW", you think that the author, who did in
fact actually use ETW, didn't do his homework?

Read the criticisms. They are not shallow.

~~~
zvrba
From the article: "It is a logging system, and it is used to record
performance and debugging information by everything from the kernel upwards."

It's apparent that he did not do his homework. ETW is much closer to DTrace
than to syslog (e.g., you can turn on and off certain events in a running
application w/o disruption).

EDIT: It seems that the author uses ETW as an excuse for writing a rant about
how an API should be designed according to his taste. Powerful, low-level APIs
are difficult to use. Simple as that.

------
BugBrother
To be fair -- I am certainly not the only one that opened that page scared of
seeing my own name. :-)

>> It’s a great time in the history of computing to be writing an article
about bad APIs (which is another way of saying it’s a terrible time to
actually have to program for a living).

It is hard to make good APIs. And for complex subjects it is probably
impossible without iterating.

But you would think the guys that did the 2nd generation of VMS were smarter
and more experienced than me? But OK, there weren't much event driven
programming in VMS, I assume.

My vote for worst API I ever read:

I remember reading the "Inside Macintosh" (pre Mac OS X) about simple file IO
and it took me multiple readings to realize that you just set _some_ of the
parameters to get all the different functionality... (The rewrite of the
Inside books were really good.)

------
aaronem
Apparently the worst API ever made is this guy:
[http://i.imgur.com/EtWh3QZ.jpg](http://i.imgur.com/EtWh3QZ.jpg)

Might want to see to the stylesheet.

