Five Common Library and API Documentation Problems
Writing documentation is never easy, even for developers who actually enjoy teaching others about things they’ve written. It’s hard to adopt a new user’s mindset after you’ve spent a long time implementing an API and, consequently, know everything there is to know about it. Many common API documentation problems are difficult to spot from a position of knowledge — unless you can recognize them from afar, regardless of your mindset.
Here are five of the most common problems I’ve struggled with — both as a developer who wanted to use an API or a library, and as a developer who had to document their API.
Common API Documentation Problems
- Not Providing a Learning Path
- Focusing on Instructions at the Expense of Concepts
- No Example Code
- Never Building from First Principles
- Not Looking Beyond Code
Want to make sure NONE of these errors are present in your API documentation? Check out our technical writing services and let’s talk!
Not Providing a Learning Path
A common problem with API documentation written by experienced developers is the lack of a learning path. A lot of libraries offer heaps upon heaps of thorough reference documentation — but no obvious starting point for learning the basics, and no pointers to help you cross the bridge between “I went through the tutorial” and “I’m a wizard”.
Not providing a learning path doesn’t just hinder the adoption of your API — it hinders its use and can spell trouble in the long run.
Let’s start with the obvious problem: an unclear learning path hinders adoption. An API that’s difficult to learn — and especially difficult to get started with — won’t go down easily. If it takes days to get something useful out of your API, it can be difficult to convince anyone to invest time in adopting it.
But more importantly, an unclear learning path keeps advanced features hidden and makes your product seem inferior. Learning to use an API’s most advanced features tends to require a lot of domain-specific knowledge.
A “Hello, World” example is short and requires little prior knowledge, in any framework (OK, almost any framework). But advanced features rely on a lot of knowledge that’s specific to your product. If developers can’t navigate their way through this scaffolding, they’ll never be able to get to the best parts, the ones where your API truly shines.
For complex APIs, a Getting Started guide is not enough. By all means, show your users how to write their first app, but don’t just leave them there!
That first app barely scratches a surface under which new users don’t really know what they’ll find, and often don’t know what to look for. Offer them a path: tell them what concepts they should look into, show them a few self-contained examples, point them at a few tutorials for your favorite features.
Where to look for inspiration
One of my favorite examples of teams that got the learning path right is Microsoft’s ASP.NET Core team. Their documentation isn’t just thorough — it also makes it easy to figure out what you need to learn, and where to learn it from. Early chapters have explicit “next steps” sections, and the transition from basics to reference is clearly structured.
Focusing on Instructions at the Expense of Concepts
Tutorials are the bread and butter of API documentation. They are the most straightforward way to get started with a new technology, and they aren’t there just to teach you things.
Going through a tutorial is a lot like taking a car for a test drive. It allows potential users to get a feel of your technology, to see what’s easy to do with it and what isn’t, to evaluate its documentation, the quality of the development tools and so on. It also offers quick gratification, which in turn makes your technology feel more approachable and gets people excited about things they can build with it.
There’s an inherent requirement to keep tutorials short. A long-winded tutorial doesn’t quite tick the “quick gratification” box and doesn’t make your technology seem more approachable. But you want to do that by keeping tutorials self-contained, not by skipping the background information.
Tutorials that consist of nothing but snippets of code and instructions about where to paste them may be quick to go through, but they don’t teach your users anything. They don’t build an understanding of your technology — but this understanding is precisely what developers in order to build something useful with your technology.
Worse, information-scarce tutorials encourage copy-paste programming, which perpetuates bugs and bad usage habits. It also tends to hinder creativity and involvement and stifles community growth. There are only so many things you can build by copying and pasting things, and none of them are particularly spectacular.
Where to look for inspiration. Say what you will about the JavaScript library churn, but it does force the community around it to be efficient in conveying information. The Angular.js tutorial is a good mix of quick gratification and useful information.
No Example Code
Focusing on providing reference material without providing any code whatsoever is one of the most common problems, and the most frustrating for developers. It also tends to have the most impact.
Lack of example code is especially problematic for developers who are just learning to use your API — that is, for potential adopters and fans.
When we think of API documentation, we tend to think in terms of reference material. As advanced users of an API, we tend to reach for the docs in order to find out things like what a parameter means, or what value we need to pass to achieve a certain behavior. Think “what errno values can read(2) set” or “how does parseInt decide what radix to use”.
But that’s only the most trivial use of documentation. Good documentation doesn’t just list knowledge, it builds understanding. And example code is one of the best ways to build it.
Example code provides context, which leverages things your readers already know and makes complexity more palatable. Have a look at System.Drawing in the .NET framework — an example that I deliberately picked because it has a lot of historical baggage behind it. It’s basically a wrapper around GDI+, an old (Windows-XP era) extension of GDI, itself one of the oldest of Windows’ subsystems.
Even seemingly trivial classes, like TextureBrush, embed a great deal of complex functionality. But note how the liberal use of example code makes all of this approachable.
Even people who have never touched GDI or GDI+ before can sort of “get it”: it shares a lot of common idioms with other APIs, like the HTML5 Canvas API.
Just by looking at a piece of example code, experienced users of other frameworks can fill in a lot of the blanks. They can make better use of your documentation because they know what kind of API to expect and therefore know what to look for and where.
Example code helps you establish idioms and best practices.
If you have a look at the sample code in the TextureBrush documentation page, you’ll notice that there’s a lot of implicit information there. You need to have a bit of scaffolding in place before you can draw anything — a formGraphics instance, for example. The brush’s texture is loaded from an image, and loading it can fail — and that potential failure has to be handled.
A lot of this information isn’t trivial to surmise. You can piece it together if you read through the complete reference of the System.Drawing namespace but that’s a lot of information to take in Example code allows you to provide all this implicit information at a fraction of this cognitive effort.
Example code provides much-needed illustration of complex concepts. One of my favorite examples is a book so good that, for many of us, it ended up superseding “official” API documentation: the late W. Richard Stevens’ UNIX Network Programming.
UNIX network programming is… famously murky. Berkeley sockets, one of the most important network programming APIs, has its origins in a very different era.
A lot of things weren’t obvious when it first came up. It was built to support a lot of protocols we no longer care about today and predates many protocols that we do care about (such as IPv6). Even things like representing an IP are somewhat non-obvious (and not because the API is brain-damaged but because there really is a lot of complexity involved there — believe it or not).
Generations of programmers learned how to use Berkeley sockets from W. Richard Stevens’ book. Not because we didn’t have access to official documentation, mind you. Between the POSIX specs and each platform’s documentation (e.g. in the form of manpages), Berkeley sockets are very well documented.
But if you’re just starting out, navigating its complexity without a pocket map — in the form of example code — is next to impossible.
One simply cannot overstate the influence of the UNP book. Twenty years after the second edition came up, I still regularly run into recently-written code that’s clearly based on W. Richard Stevens’ examples.
Where to look for inspiration. My all-time favorite here is, by far, Qt. Qt’s documentation is very well illustrated and makes even the hardest things look simple.
Django also gets this right. And Twitter gets a lot of things wrong with their API, but documentation isn’t one of them.
Never Building from First Principles
Relying on your users’ prior experience is always a good idea. Prior experience is valuable not only because it saves you the trouble of explaining things that are already known.
It’s valuable because it embodies a lot of things besides “raw” knowledge: it also includes lessons about what not to do, knowledge about what to expect from a system, about surprisingly relevant corner cases and so on.
But sometimes, whether you like it or not, you simply have to build from first principles. You have to sit down and write out things that will not result in your users writing a single line of code — but knowledge of which is fundamental to them ever writing anything useful.
Take Android’s application architecture, for example. Building applications out of reusable parts isn’t exactly arcane knowledge, and it wasn’t arcane knowledge back in 2008, either.
Concepts like services and activities aren’t entirely foreign to experienced developers coming from other fields. But there is enough domain-specific knowledge about Google’s implementation and architecture, and enough jargon, that they really need to build a thorough understanding of these concepts from the ground up.
And they do!
These concepts are extensively treated, first from a bird’s eye perspective to give you a general idea about how they’re used, and then in significant detail.
Some things need to be explained from scratch. Things that are completely novel, or which your API does in a very special way, need to be explained from the ground up, and you shouldn’t shy away from it.
Building understanding from the ground up reduces the risk of misunderstanding. That, in turn, translates into more successful developers, fewer support requests, and a more confident community.
It also makes developers more confident about their reasoning. A solid understanding of your framework from first principles makes it easier for people who haven’t written it to reason about problems that they encounter, or about the best way to achieve something.
Finally, it makes it easier to avoid pitfalls and to reason about performance, because building knowledge from first principles familiarizes developers not only with your API’s idioms but also with the design constraints and the reasoning that went behind your choices. This helps developers make the right choices on their end, too, and to write idiomatic code that uses your API in the best possible way.
Where to look for inspiration. Android’s documentation is an excellent example of how to deal with the need to explain things from scratch.
It’s not perfect, and some of the more obscure sections of the API reference are downright unhelpful — but the way it introduces concepts like Activities, Services, and Intents is top-notch. ZeroMQ also has to explain a lot of concepts (almost) from scratch, and the documentation does so remarkably well.
Not Looking Beyond Code
Libraries, application frameworks, and web APIs are all about code. Their documentation is used by developers to write code. What else could documentation include?
How about:
Setup instructions: how do you compile this thing? How do you link to it in your project? Are there any compile-time flags that I need to know about?
Complex libraries require a lot of things besides “merely” compiling them — although that may be more complicated than just running make or hitting F9 (here’s just one random example).
Development tools integration. What tools does your development team use? If any of them requires some non-trivial setup and rewards the effort with a better development experience, why not share that with the whole world? Here’s a good example from Flutter.
These things can be immensely helpful, especially for beginners. They also smooth things out a little: a new, unfamiliar API is slightly easier to deal with if you can do it in your favorite environment.
Information on API stability. Are there any parts of your API that are work-in-progress and likely to change? Are there any parts about which you make substantial backward compatibility guarantees?
Don’t leave these things unsaid!
The former provides valuable orientation for long-term projects, who need to account for API changes and maintenance in the long run.
And especially don’t leave out the latter.
In a world where you can make a decent living as a consultant just from updating code to work with this year’s APIs, stability is an asset that you shouldn’t sell short!
Tips about common pitfalls and good practices. There is information, such as whether a function is thread-safe or not, which is firmly in the “if you know it, document it” bag. But what about less critical information, like performance tips or implementation details?
My advice? Err on the side of completeness. If you can imagine a scenario where some information might be useful, then document it. Someone will run into it sooner or later.
Here’s a good example from one of my favorite RTOSs ever, QNX. The documentation of the MessageReceive function (it does exactly what it sounds like it does) includes this little gem: “If a message is waiting on the channel when you call MsgReceive(), the calling thread doesn’t block, and the message is immediately copied.”
This looks like a minor implementation detail — but it’s not! The folks behind QNX “got” that making the scheduler aware of message-passing is critical to making message-passing efficient. Their documentation makes this clear every step of the way — including here — and these implementation details give you valuable information about how to make this architecture work in your favor.
Cover image source.