The term “API” seems to present something of a Rorschach Test for software developers. Web developers think of APIs as REST endpoints and WSDLs. In contrast, desktop developers may think of APIs as “library code” written by developers from other organizations. Folks writing low-level code like drivers? API provides the hooks into OS system calls (emphasis mine).
I understand this mild myopia that can happen, but the bigger picture matters. API offers a much more generic way of thinking. I personally like the definition from tech terms:
An API is a set of commands, functions, protocols, and objects that programmers can use to create software or interact with an external system. It provides developers with standard commands for performing common operations so they do not have to write the code from scratch.
An API defines how other programmers interact with your software. Thus each of the personas I mentioned has the right answer, but a small slice of it. As you write code, ask yourself about the user of that code. If that user interacts with your code via code of their own, you’re building an API. And before you ask, yes, that even applies to other developers in your company or even your team that use your code.
Consequently, understanding properties of good APIs carries vital importance for our industry. It makes the difference between others building on your work or avoiding it. And, let’s be honest — there’s a certain amount of professional pride at stake. So what makes APIs good? Let’s consider some characteristics of good APIs.
First and foremost, I’d like to stump for simplicity. Programmers tend to solve complex problems, making it easy to let that complexity bleed through to users of our APIs. Keeping them simple requires work, and sometimes presents a serious challenge.
Our tendency to want to be helpful adds to the difficulty. Often, programmers want to offer several different ways to do something. “Maybe they want to pass these dependencies in through a constructor. But what if they prefer to use setters? We should make both available.” The road to complexity can be paved with good intentions.
Fight the urge to add undue complexity, even when you think it might help. Good APIs offer simplicity, and it takes a good bit of effort to preserve that simplicity.
Provide Useful Abstractions
Next, consider the concept of abstraction. When you successfully hide details from users of your API, leaving only the essentials, you offer them abstraction.
Abstraction examples abound in our world. Device drivers abstract the details of dealing with vendor hardware. Threading models provide an abstraction for worrying about scheduling execution at the OS level. The OS itself provides an abstraction over the differences in core computer hardware. Even your programming language abstracts away the details of writing machine code.
Well written code provides abstractions, and APIs are no exception. Your API should hide the details of what it does from your users while making itself useful to them. If your users need to dive into your code or execution to understand, you have provided a poor abstraction.
Simplicity and good abstractions will take you a long way. But you can go even further when you write a “discoverable” API. This has to do with how easily a novice can become productive using your code.
As a helpful parallel, I’ll invoke a non-code example. Think of what made the original iPhone revolutionary from a UX perspective. It had a screen and a single button. Its apps left things hanging off the side of the screen to invite and practically beg you to discover that you could advance your usage by swiping left and right. Unlike previous devices, with tiny buttons and non-stop context menus, this discoverable device let even the least savvy users get going quickly.
Strive for this with your APIs. Understand that your users will hack and experiment before they ever pick up a manual or call to ask. Plan and design accordingly, both with documentation/examples and with the sort of self-describing access points that lend themselves to discoverability. A method called “GetLastNameFromOrder(CustomerOrder orderToQuery)” practically begs you to use it for its intended purpose.
Consistent and Symmetrical
Assuming you have crossed off the previous considerations, examine your API for consistency and for symmetry. Consistency is relatively easy to understand. Don’t call them “users” sometimes and “customers” at other times. Name the same things the same way. Have a common style. Make your API predictable.
Symmetry presents a slightly more subtle consideration. When I talk about symmetry, I mean a tendency to close open loops in a way that your users expect. For example, if you have an API for file access that lets you call Open(string filename), you should also offer a Close(string filename method). Open and close have opposite meaning, so offering both creates symmetry as concerns that operation. Calling the Close method “Destroy” or, worse, not having a close method, would create a great deal of confusion.
You can generally achieve this characteristic in your API by applying a critical eye to what you expose to users. Read through it the way a proofreader might, checking for consistency and symmetry. Then rename and alter as needed.
Follow the Principle of Least Astonishment
For the last consideration in the mix, I want to introduce you to the cleverly named “principle of least astonishment.” This holds that “a system should behave in a manner consistent with how users of that component are likely to expect it to behave; that is, users should not be astonished at the way it behaves.” To put it more bluntly, don’t write APIs that flabbergast your users.
My example above regarding symmetry could also be said to violate this principle. But let’s change it a bit to make it more obvious what this principle means. Imagine that your file API had an Open(string filename) method and then it asked you to close files by calling Open() with no parameters. Also, whenever you call Close(), it sends an email to some guy named Bill Smith, for some reason. Take your reaction to that proposition, and you have the “astonishment” factor nicely captured.
Good APIs do not have properties that cause their users to exclaim, “but that makes no sense!” or “how would I ever have known that?!” I can think of no faster way to lose the trust of your users than to present them with an API that does something tangential to or opposite of what it claims to do.
Think of Your API as a Product
Simple, useful, discoverable, consistent, and predictable all describe not only good APIs but good products. That’s no accident. When you write APIs, you create a product. Whether you think of it that way or not and whether you actually sell it or not, you have a product on your hands, intended for use by someone else.
As developers, we can easily lose sight of that. Subject to the curse of knowledge, we assume that our API users, fellow programmers, will know what we know and figure out what we’ve figured out. We don’t naturally think of them as our end users or our customers.
Overcoming that tendency and putting ourselves in their shoes is the unifying theme behind good API design. So when you write your next API, put yourself in the shoes of your customer, and imagine what you would want.