09 Apr 2018

Notes on Good API Design

How To Design A Good API and Why it Matters is a great talk that Joshua Bloch gave at Google. It's actually over 10 years old now but still very relevant. I recently rewatched it and this post contains the summary notes I made.

Why Good APIs Are Important

APIs are a massive asset. They really can make a company if they’re good. This also works the other way, such that a ‘bad’ API can be a massive liability and headache. Think long lists of support issues, or an API that is relied upon but broken in several ways. Such an API in production is now very difficult to fix and expand without lots of work and likely disruption.

As an interesting aside, James Acres, who is the Lead Software Developer at Lumina Learning, has a good post on handling a monolithic API. Note that the solution was required for the business to move forward!

As a programmer, it’s good to get into the habit of thinking in terms of API design, even if we’re working on something that wouldn’t quite fit into our traditional view of what an API is. This is because such thinking lends itself to better code by giving more thought to the logical structure and interaction with other components.

Designing an API

Start with a one page initial specification. Very important that we keep it short to start with. This means it’s easy to modify. It’s also easy to read and digest.

Start showing this short document to the relevant stakeholders and consider their feedback. Build up a set of use cases for the API. Use this information to flesh out the spec. document.

Write to the API before implementing it. This will help validate the early spec and avoid nasty surprises. Expect to make mistakes in the design and so allow some future ways for the API to evolve.

Write Example API Uses. These are very important. Getting it right here encourages correct usage going forward, plus these examples will likely be viewed many times as future code is written against the API. These examples should be exemplary (Joshua suggests spend ten times as much time on examples as normal production code).

Good API Principles

Make it easy to learn and use - The API should encourage the correct usage by being easy to learn and use (think along the lines of still making sense without documentation, not that you should leave it undocumented!). This also means it should be hard to misuse. Make the API just powerful enough to do what’s required.

Do one thing, do it well. It should be easy to explain, it should name easily/simply. If parts of the interface don’t name well you likely have a wider problem. Good names drive good design and give affordance for correct usage.

When in doubt, leave it out. Keep the API as small as possible. Much easier to add later, whereas taking functionality out is near impossible without a massive breaking change. It should have a good power-to-weight ratio. End user of the API should be able to do a lot without learning a lot.

Don’t let implementation leak out. For example, don’t throw a SQL exception. The users of the API should not know or care how it’s implemented. The exceptions should be at the same level as the rest of the API.

Minimise Accessibility of Everything. Keep methods/variables for your class as private as possible. This encourages uncoupling and also restricts the public interface to a smaller/clearer set of components.

Names matter. An API is like a little language. It’s important to learn and speak the language. The two big points here are to avoid abbreviations and be consistent. Getting it right can read like prose:

if (Car.speed() > 2 * SPEED_LIMIT)
    speak.generateAlert("Watch out for the cops!");

This example is easy to read and makes sense without having to look up awkward names or method documentation.

Document well. No documentation of the API means people either guess, or look at the code. Both are bad. Looking at code is bad in a more subtle way, as the API becomes over-specified and harder to change. Users of the API can start relying on how something is implemented.

Do no warp API to gain performance. Design for the long haul. To further the point, have the API be predictable and not do slightly different things in some places to bypass bad performance. Bad performance can likely be fixed in the future, whereas strange API behaviour that becomes relied upon cannot.

Do what is customary for the platform. Mimic patterns in core APIs and language. The thinking here is that those who know and use the core APIs will easily be able to use yours.

Principles for API Methods

Don’t require the client to do anything the module can do. This leads to lots of back and forth and boiler plate code. Boiler plate code just opens more opportunities for bugs!

Don’t violate the principle of least astonishment. This is worth extra implementation, even if it brings reduced performance. I enjoyed Joshua’s real example at this point:

public class Thread implements Runnable {
    // Tests whether current thread has been interrupted.
    <strong>// Clears the interupetd status of current thread</strong>
    public static boolean interruped();

This violates least astonishment. It should really be called clearInterrupt() or similar, and as a secondary action return the old interrupt status to you. Instead the name implies it won’t make any changes, and thus lots of broken code is out there using this.

Fail fast. Report errors as soon as possible, ideally at compile time! If the user passes in garbage data, you should tell them as soon as possible.

Allow data to be returned in programmatic form. If data is only provided as some printable string then users will need to parse this to get the data. This means the string format is then relied upon as well.

Use appropriate parameter and return types. Be as specific as possible. Don’t use a string if a better type exists (Easy example being boolean instead of something that returns ‘yes’/’no’).

Be consistent with parameter ordering across methods. This is especially problematic if the type of parameters are the same, as the user won’t necessarily know they’ve done something wrong.

foo_copy( src dest)
bar_copy( dest src)

Clearly this is going to catch someone out sooner or later!

Three or fewer parameters is ideal. Try to avoid lots of the same type of parameter, as messing up the order won’t show as an error at compile time. If you need more consider breaking up the method, or passing in an object/hash/similar that logically groups a set of the parameters.

Back to posts