In a previous blog, I talked a bit about the hazards of coding to an implementation and not a specification, based on 1980s home computers. While the specifics and peculiarities of that case is hopefully confined to old hardware, the lessons are still worth contemplating. There is a modern variant of this phenomenon that is based on open-source software, and that I must admit to feeling a bit annoyed by. Fundamentally, the question is this: when figuring out how to use an API – should you look at the documentation or the implementation?
A software API is (as we all know) is an interface provided by some software for use by other software. I use the word “API” in a very broad sense – traditionally, it used to mean a set of functions with associated data types, but I also think the term applies equally well to programming frameworks, object-oriented base classes, or even the command-line parameters of stand-alone binaries. Anything you can use to reuse existing functionality, really. The API can be provided by an operating system, an applications framework, a domain-specific library, a software program supporting plugins, utilities in the Linux style, or cloud services. The key is that an API provides an abstraction for your code, hiding some recurring programming problem or complex useful behavior inside a black box.
Today, unlike for 1980s home computers, almost no software is written talking directly to the hardware. APIs are a core part of the software ecosystem and learning to use an API is arguably a more valuable and intricate skill than learning to code in a particular programming language. It is not the language that lets you get things done, it is the set of APIs you are using (with the above very broad definition).
The Interface is Key
A software API is an interface just like a hardware interface. It has to be documented so that users can use it. Documentation can encompass user guides, reference materials, formal specifications, example code, etc. To me, the documentation and definition of the interface is the key part of the API – when using the API, you should consult the documentation and definition. That should be enough to make use of the interface. Ideally, you can think about what to do, not how the system behind the API gets the job done.
However, sticking to the documentation and definition isn’t always all that easy. Especially when things get complicated and the source code is available.
Peeking at the Implementation?
Some programmers are of the opinion that having the source code of an API you are using is preferable to documentation. With the code, you can look at that to understand what an API function in a library actually does. This follows the logic that the code is truth, while documentation is at best a guide to how things work. Documentation is a bit suspect and might not be entirely up-to-date with the changes to the code.
However, that approach is broken in several ways if we consider the API as a long-term abstraction or product, and not just as an instantaneous snapshot of how things work.
First of all, it almost inevitably ends up with coding to the implementation instead of the intention of the interface or specification. In future versions of the software, the implementation might change, and code that relies on the details of a certain implementation will quite likely break. This has happened many times, especially when an interface evolves into the future and is implemented on multiple different platforms. The API design might include specific constraints on inputs or usage that are not strictly needed by any specific implementation, but which are really important when it comes to keeping the same API working across generations of expanded functionality and across multiple underlying platforms.
Second, it breaks the concept of the opaque and abstracted interface. This affects both the user and the implementer. As a user, I should not have to understand the implementation to use an API or library. The point of an interface or API is to package up functionality as something that can be used without knowing the implementation. This should reduce the mental load on the programmer, as she or he only has to work with the concepts of the API, and not the much bigger code behind the scenes. For the implementer, having users peek at the implementation complicates future modifications, since it creates the risk of implementation-dependent code.
Third, it might serve as an excuse to skimp on documentation. If you design an API with the mindset that users will just open up and dig through the source to understand it anyway, the motivation to provide documentation is reduced. There is a school of thought that even thinks that source code is the best documentation, which is something I definitely do not agree with. Source code, even with plenty of comments, is at the wrong level of abstraction. An interface or API is a higher-level construct that is designed in its own right. Not just a property of the code implementation.
Source Code is Very Handy
While source code should not be used in lieu of documentation, having access to it is still immensely useful. It really helps when debugging problems and reporting issues. Getting a full stack trace through libraries with function names and arguments rather than a set of pointer values provides much more information. Looking at the code where something goes wrong often helps illuminate what is going wrong.
Additionally, when your API-using code follows the specification and documentation and things still do not work as they should, it sometimes makes sense to dig into the implementation to pin-point how the implementation deviates from the specification. This can make a bug report more useful. In an open-source project, a patch for the issue might be provided along with the report. However, an analysis based on source code can also be entirely off. There is a risk that symptoms and root causes are confused, since just digging into a complex source code base is quite hard.
Experts and Novices
The attitude to source code and interfaces might also be different between novices and experts. I tend to identify with novice users who want the hand-holding of documentation and examples, and do not have the experience to make good use of the code.
For an expert user familiar with an API and underlying product, there is naturally more curiosity about the underlying implementation. In addition, familiarity tends to breed some skepticism about the precise veracity of the documentation. There are always bugs, and the more you have seen the more reason to be a bit skeptical.
Stick to the Abstraction!
My opinion is clear. APIs should be treated as opaque abstractions. Sticking to this principle serves as a nudge for programmers to document their designs and to carefully design the interface. Doing that describing and documentation, preferably with many runnable examples, tends to force a review of the design. That review, in turn, will tend to reveal issues with the design overall, resulting in a better interface and a better product. Furthermore, explainability becomes a factor when you are forced to explain the API, which is a significant benefit. This tends to guide the design towards easier-to-use APIs and patterns.
On the other side, “just look at the open source” can be an excuse to not work through the process fully, but just drop it when it feels done. It can also mean that a user just looks inside and creates something that works in the here and now, but that might well fail if the implementation changes. It could also mean that users don’t report issues with the API or docs.