API users & API wrappers

Suppose you have a sparse RAM API, something along the lines of:

  • add_range(base, size)
  • write_ram(base, bytes)
  • read_ram(base, size)

People use this API for things like running a simulated CPU:

  1. define the accessible memory with add_range()
  2. pass the initial state to the simulator with write_ram()
  3. run the simulation, get the final state with read_ram()

Suppose this API becomes a runaway success, with a whopping 10 programmers using it (very little irony here, >95% of the APIs in this world are used exclusively by their designer). Then chances are that 9 of the 10 programmers are API users, and 1 of them is an API wrapper. Here's what they do.

API users

The first thing the first API user does is call you. "How do I use this sparse thing of yours?" You point him to the short tutorial with the sample code. He says "Uhmm. Errm…", which is userish for "Come on, I know you know that I'm lazy, and you know I know that docs lie. Come over here and type the code for me." And you insist that it's actually properly documented, but you will still come over, just because it's him, and you personally copy the sample code into a source file of his:

add_range(0x100000, 6) # input range
add_range(0x200000, 6) # output range
write_ram(0x100000, "abcdef")
# run a program converting the input to uppercase
print read_ram(0x200000, 6) # should print "ABCDEF"

It runs. You use the opportunity to point out how your documentation is better than what he's perhaps used to assume (though you totally understand his frustration with the state of documentation in this department, this company and this planet). Anyway, if he has any sort of problem or inconvenience with this thing, he can call you any time.

The next 8 API users copy your sample code themselves, some of them without you being aware that they use or even need this API. Congratulations! Your high personal quality standards and your user-centric approach have won you a near-monopoly position in the rapidly expanding local sparse RAM API market.

Then some time later you stumble upon the following code:

add_range(0x100000,256)
add_range(0x200000,1024)
add_range(0x300000,1024)
...
add_range(0xb00000,128)
...
add_range(0x2c00000,1024)
...

Waitaminnit.

You knew the API was a bit too low-level for the quite common case where you need to allocate a whole lot of objects, doesn't matter where. In that case, something like base=allocate_range(size) would be better than add_range(base,size) – that way users don't have to invent addresses they don't care about. But it wasn't immediately obvious how this should work (Nth call to allocate_range() appends a range to the last allocated address, but where should the first call to allocate_range() put things? What about mixing add_range() and allocate_range()? etc.)

So you figured you'd have add_range(), and then whoever needed to allocate lots of objects, doesn't matter where, could just write a 5-line allocate_range() function good enough for him, though not good enough for a public API.

But none of them did. Why? Isn't it trivial to write such a function? Isn't it ugly to hard-code arbitrary addresses? Doesn't it feel silly to invent arbitrary addresses? Isn't it actually hard to invent constant addresses when you put variable-sized data there, having to think about possible overlaps between ranges? Perhaps they don't understand what a sparse RAM is? Very unlikely, that, considering their education and experience.

Somehow, something makes it very easy for them to copy sample code, but very hard to stray from that sample code in any syntactically substantial way. To them, it isn't a sparse RAM you add ranges to. Rather, they think of it as a bunch of add_range() calls with hexadecimal parameters.

And add_range() with hex params they promptly will, just as it's done in the sample. And they'll complain about how this API is a bit awkward, with all these hex values and what-not.

API wrappers

If there's someone who can see right through syntax deep into semantics, it's the tenth user of your API, or more accurately, its first wrapper. The wrapper never actually uses an API directly in his "application code" as implied by the abbreviation, standing for "Application Programming Interface". Rather, he wraps it with another (massive) layer of code, and has his application code use that layer.

The wrapper first comes to talk to you, either being forced to use your API because everybody else already does, or because he doesn't like to touch something as low-level as "RAM" so if there's already some API above it he prefers to go through that.

In your conversation, or more accurately, his monologue, he points out some admittedly interesting, though hardly pressing issues:

  • It's important to be able to trick a program using the sparse RAM API into allocating its data in specific address ranges, so that the resulting memory map is usable on certain hardware configurations and not just in simulations.
  • In particular, it is important to be able to extract the memory map from the section headers of executables in the ELF and COFF format.
  • Since add_range() calls are costly, and memory map formats such as the S-Record effectively specify a lot of small, adjacent ranges, there is a need for a layer joining many such ranges.
  • An extensible API for the parsers of the various memory map formats is needed.

When you manage to terminate the monologuish conversation, he walks off to implement his sparse RAM API on top of yours. He calls it SParser (layer lovers, having to invent many names, frequently deteriorate into amateur copywriters).

When he's done (which is never; let's say "when he has something out there"), nobody uses SParser but him, though he markets it heavily. Users won't rely on the author who cares about The Right Thing but not about their problems. Other wrappers never use his extra layers because they write their own extra layers.

However, even with one person using it, SParser is your biggest headache in the sparse RAM department.

For example, your original implementation used a list of ranges you (slowly) scanned through to find the range containing a given address. Now you want to replace this with a page table, so that, given an address, you simply index into a page array with its high bits and either find a page with the data or report a bad address error.

But this precludes "shadowing", where you have overlapping segments, one hiding the other's data. You thought of that as a bug in the user code your original implementation didn't detect. The wrapper thought it was a feature, and SParser uses it all over to have data used at some point and then "hidden" later in the program.

So you can't deploy your new implementation, speeding up the code of innocent users, without breaking the code of this wrapper.

What to do

Add an allocate_range() API ASAP, update the tutorial, walk over to your users to help replace their hex constants with allocate_range() calls. Deploy the implementation with the page table, and send the complaining wrapper to complain upwards along the chain of command.

Why

Your users will switch to allocate_range() and be happy, more so when they get a speed-up from the switch to page tables. The wrapper, constituting the unhappy 10% of the stakeholders, will have no choice but fix his code.

Ivan drank half a bottle of vodka and woke up with a headache. Boris drank a full bottle of vodka and woke up with a headache. Why drink less?

Users are many, they follow a predictable path (copy sample code) and are easily satisfied (just make it convenient for them to follow that path). Wrappers are few, they never fail to surprise (you wouldn't guess what and especially why their layers do), and always fail to be satisfied (they never use APIs and always wrap them). Why worry about the few?

The only reason this point is worth discussing at all is that users offend programmers while wrappers sweet-talk them, thus obscuring the obvious. It is natural to feel outrage when you give someone an add_range() function and a silly sample with hex in it, and not only do they mindlessly multiply hex numbers in their code, but they blame you for the inconvenience of "your API with all the hex in it". It is equally natural to be flattered when someone spends time to discuss your work with you, at a level of true understanding ("sparse RAM") rather than superficial syntactic pattern matching ("add_range(hex)").

He who sees through this optical illusion will focus on the satisfaction of the happy many who couldn't care less, securing the option to ignore the miserable few who think too much.

19 comments ↓

#1 The Fiddler on 03.05.10 at 11:06 am

So, so true. I've had this scenario play out pretty much word for word. More than once!

#2 anon on 03.05.10 at 12:02 pm

Peer Reviews help
a) the API developer to find others need
b) the API users in the way that they know that there is a API at all
c) the smart API users in the way that they know understand the API
d) the dump API users in the way that they get a free example
e) the wrapper if he get encouraged to refactor/improve the original code rather than writing a wrapper

This is at least my experiance. I'm still a freshman and I learn usually a lot from code reviews (my code & foreign code)
It is always sad to see how many implementations exist for exactly the same issue – and the most attractive solution (which may not be the best) is copied over and over by others.

#3 Yossi Kreinin on 03.05.10 at 1:55 pm

@The Fiddler: did this actually work in a similar way with OpenTK?

@anon: smart/dumb is not the distinction I made (I'm not sure you intended it to be a way to spell the distinction I made; it's the "free example" part that makes me think this is what you meant). Anyway, I don't think user/wrapper and dumb/smart correlate, and if there is a correlation, I'd guess it to be weak and I wouldn't guess which way they correlate. Even "naive"/"sophisticated" is the wrong distinction; I don't know a way to reduce it to cognitive traits, I only know the behavioral distinction – "go with the flow of the sample code"/"go against the grain wrapping things".

That aside, you sound like an optimist (spreading knowledge through reviews and other educational means, sad how Tower-of-Babelish and otherwise suboptimal things get); well, you've arrived at a very pessimistic site.

#4 mathrick on 03.06.10 at 3:08 pm

Still, it's better to go with the optimism in this case, since code reviews do help in more other ways. And if they also happen to pre-empt some wrapping and/or mindless copying attempts, well, all the better for you! Not to mention that pessimism can create its own causes by the way of the broken window effect. So while it's true that sometimes you end up in a shithole with no way out, in general it pays to be as optimistic as possible.

#5 Yossi Kreinin on 03.06.10 at 11:11 pm

@mathrick: code reviews are a tricky subject so I won't argue about that at the moment – except that the wrapper type is well-versed in the software bullshit jargon and has stamina for endless arguing, hence a code review or any other social decision-making mechanism is unlikely to help, though a shotgun could.

As to the broken window effect – I agree, hence the tutorial for the API in the hypothetical example, but there's a difference: here it is "you" the API author who's keeping the windows non-broken, so being "pessimistic" here (not doing it because hey, the world sucks anyway) amounts to being lazy; however, the way "they" use APIs isn't something you control, so being "optimistic" here is, well, also being lazy (count on others to be thoughtful where it's easy to predict they won't and where you and I quite likely wouldn't be and where simple measures on your side could ameliorate the consequences of someone else's predictable and hardly avoidable behavior).

#6 harald on 03.07.10 at 10:01 am

Oh dear, I recognised myself in your wrapper description. But some apis are just so tedious! Take stl's file handling.

Please, take it!

#7 Yossi Kreinin on 03.07.10 at 11:34 pm

C++ doesn't have much file handling in its standard library, does it? There are the more elaborate boost APIs but I don't touch boost with a laser beam so I wouldn't know much about that; the standard interfaces include fstream, which is, well, usable, and stuff like ostream_iterator, which no wrapping can save.

#8 k5.user on 03.08.10 at 10:50 am

"My name is k5.user, and I have written many API wrappers and forced others to use my wrapper."

Confession, good for the soul.

#9 anon on 03.08.10 at 1:49 pm

well, yes, I'm (still) an optimist.
A collegue mentioned today, that he have never found a way to describe the intetion from his framework. We both fully agreed, that minimal code examples cannot show that. So I thought I forward the question to you:
Do you think that people will use the API in a proper way when they understand the intention of the framework? (we do)
How to describe this intention?

#10 anon on 03.08.10 at 1:53 pm

I also should mention that my collegue had some terrible examples how users misused and abused his frameworks ending up in changes in within the framwork so that someone elses wrapper works.
So he is on the pessimistic side, too

#11 Yossi Kreinin on 03.09.10 at 4:51 am

@anon: I think intentions can be conveyed either through sample code or, more subtly, through a tendency to shape the user's code in certain ways, but hardly through words – generally, the consumer can almost never reach a level of thoughtfulness about the product anywhere near that of the producer, so it's very hard to correct bad user behavior through education (unless people are willing to invest lots in education – say, when the "consumer" is a musician learning to play a violin, and even then you must make your violin compatible with existing education rather than hoping to reshape the education to suit your newer violin).

That said, I'm somehow very curious about the details – what it is that you're doing and what it is that people don't get about it, so if you want to discuss this either here or at Yossi.Kreinin@gmail.com, you're most welcome.

@k5.user: may God forgive you.

#12 gus3 on 03.09.10 at 8:31 pm

"Do you think that people will use the API in a proper way when they understand the intention of the framework?"

Maybe, but consider: They'll see possibilities in it that you never dreamed of.

Following on the above example of the violin, it helps to consider how scandalous "The Four Seasons" was in Vivaldi's day. One critic mused, "If rapid, high tones are a sin, Signore Vivaldi will have much to account for."

#13 Yossi Kreinin on 03.10.10 at 1:03 am

@gus3: it usually isn't a Vivaldi who is the problem.

#14 Kragen Javier Sitaker on 04.24.10 at 9:42 pm

It seems like the lowest-energy solution would be for the API wrapping guy to send you a patch that adds allocate_range(). Then he would have to care about your users' problems in order to get you to accept the patch, you'd have more people working on your users' problems, and he'd have more people using his code. Wins all around, right?

#15 Yossi Kreinin on 04.24.10 at 11:02 pm

Yes, but that's not what API wrappers do. API wrappers don't extend, they wrap, hiding the original API behind their own API operating with different concepts. People who are so cooperative and thoughtful that they gracefully extend are a distinct group that I didn't mention because of its apparently tiny size.

#16 Kragen Javier Sitaker on 04.29.10 at 12:23 pm

Almost nobody is successful at extending gracefully on their first attempt, especially in a company with code ownership; instead they blunder around violating all your style rules and encapsulation, while still adding new features that people actually need. Getting somebody up to speed enough that they can give you a patch that you'll want to accept without rewriting it can take a certain amount of time.

#17 Yossi Kreinin on 04.30.10 at 7:25 am

I'm a step behind you – I believe one will usually have hard time even finding someone with enough interest in the problem plus the right psychological profile.

#18 Nikolai Kondrashov on 06.01.10 at 10:16 am

I think your "wrapper" type is an instance of the general immature programmer type. These like *adding* complexity, instead of removing it, and then enjoy themselves being able to construct such universal abstractions. A young and capable mind contributes to this obsession. I was one myself. Still, this could be a problem even for experienced programmers, they have to keep themselves in check constantly too :)

Thanks for the C++ FQA, BTW :)

#19 Yossi Kreinin on 06.01.10 at 10:37 am

Well, there exist wrappers who are mature in pretty much every sense of the word, so whether you and I like what they've matured into or not, they surely ain't gonna get any maturer. As to love of complexity as the psychological root cause – can be part of it, though there can be other angles as well, for instance, repressed fear of the application domain (wrap it to hide the horror from the eyes), an unbendable belief in The Right (wrap it to right its wrongs), etc. As far as I'm concerned as an API maintainer, wrappers are a single cluster; to what extent they are psychologically homogeneous is an interesting question.

And, you're welcome :)

Leave a Comment