Stories about Software


APIs and the Principle of Least Surprise

Editorial note: I originally wrote this post for the Monitis blog.  You can check out the original here, at their site.  While you’re there, have a look at some of the other authors for their blog.

I remember something pretty random about my first job.  In my cubicle, I had a large set of metal shelves that held my various and sundry programming texts.  And, featured prominently on that shelf, I had an enormous blue binder.

Back then, I spent my days writing drivers and custom Linux kernel modules.  I had to because we made use of a real time interface to write very precisely timed machine control code.  As you might imagine, a custom Linux kernel in 2005 didn’t exactly come with a high production quality video walking users through the finer points.  In fact, it came with only its version of the iconic Linux “man pages” for guidance.  These I printed out and put into the aforementioned blue binder.

I cannot begin to tell you how much I studied this blue binder.  I pored through it for wisdom and clues, feeling a sense of great satisfaction when I deciphered some cryptic function example.  This sort of satisfaction defined a culture, in fact.  You wore mastery of a difficult API as a badge of honor.  And, on the flip side, failure to master an API represented a failure of yours.

Death of “Manual Culture”

What a difference a decade makes.  No longer do we have battleship gray windows applications with dozens of menus and sub-menus, with hundreds of settings and thousands of “advanced settings”.  No longer do we consider reading a gigantic blue documentation binder to be a use of time.  And, more generally, no longer do we put the onus of navigating a learning curve on the user.  Instead, we look to lure users by making things as easy as possible.

Ten years ago, a coarse expression described people’s take on this responsibility.  I’ll offer the safe-for-work version: “RTM” or “Read the Manual.”  Ten years later, we have seen the death of RTM culture.  This applies to APIs and to user experiences in general.

The Principle of Least Surprise

A while ago, I wrote a post about good APIs.  In this post, I briefly mentioned conformance to the principle of least surprise (aka principle of least astonishment) as one characteristic of a good API.

Simply put, this principle holds that a given operation’s result should be, “obvious, consistent, and predictable, based upon the name of the operation and other clues.”  But you might find the contrapositive more amusing and memorable.  If your API does things that astonish the user, you’re failing at the principle of least surprise.  For example, if you write a database driver whose CloseConnection() method opens a second connection, your users will find this astonishing, to put it mildly.

While abiding by the principle of least surprise represents only one component of building a good API, it represents the core component of building a discoverable API that people want to use.  If you fail at this, your API goes right back to blue binder territory and, in this day and age, you’ll lose out to a competitor that understands this principle.

So let’s take a look at how you can ensure your API doesn’t surprise people in a bad way.  I’ll lay out some simple steps you can take to make your API one that people want to use.

Naming, Naming, Naming

First and foremost, give a lot of thought to how you name anything consumed by your users.  You might have heard the joke/cliche, “there are two hard things in computer science: naming, cache invalidation, and off by one errors.”  Humor aside, you’ll notice naming featured prominently as a “hard thing.”

It absolutely is hard.  But it’s also incredibly important, so that just means you need to make the effort.  Giving things the right name can mean the difference between an intuitive API and a surprising one.  Make sure you name methods and variables in ways that clearly and unambiguously communicate their intent.  And once you feel good about it, use other people to check.  Go ask another developer how they think they would use your code and see if they get it right with the names you’ve used.

Avoid No-Ops and Non-Implementations

I’ll go from offering advice on something of an art to advice on something utterly straightforward.  When you write methods for your users to consume, don’t get sloppy.  Make sure you don’t have do-nothing methods or, worse, methods that throw “not implemented” exceptions.

As a user, I’ll probably explore your API with some kind of Intellisense or using examples from your documentation, depending on the API medium.  This means experimentation via trial and error.  Imagine the effect of a no-op — a method that does literally nothing.  Imagine how much time I’ll waste before I finally conclude that the method has no effect.  And, when I do, I will find this astonishing.

And, speaking of astonishing, imagine my reaction to encountering a “not implemented” exception.  Then why include it in your production code?!  The only reason that comes to mind, beyond oversight, is that you’re implementing an interface or inheriting from a class and you don’t want it to implement the method in question.  And, in this case, you run afoul of the Liskov Substition Principle — a problem which, you guessed it, surprises users.

Steer Clear of Non-Obvious Preconditions

In a similar, subtler vein, consider the notion of preconditions.  Preconditions define conditions that must be satisfied prior to invoking a method.  For example, consider a hypothetical divideBy(dividend, divisor) method.  With a basic knowledge of arithmetic, you know that you’ll have an undefined result should you divide by zero.  Thus, you have “divisor != 0” as an obvious precondition of this method.  You cannot pass it a zero dividend and expect it to execute properly.

I’ve described here an obvious precondition.  The trouble comes when you have non-obvious ones.  Imagine if, for instance, I had a method called fetchAllContacts() as part of the API for some hypothetical CRM system.  “That seems like a good, safe method to call to start getting to know this API,” you think.  Wrong!  That method throws an exception unless you first set some integer global variable called start_cond to at least 150.

Why?  It’s a mystery to all of us.  And, it’s also a non-obvious precondition that will utterly flummox API consumers.  Don’t fall into this trap.  Take extra care to make sure that your users will find preconditions intuitive.

Don’t Subject Your Users to “Spooky Action”

Einstein once coined the phrase, “spooky action at a distance” to deride the concept of quantum entanglement.  He objected to the notion that doing something to an object could “magically” affect some other, apparently related object some distance away.  Misko Hevery appropriated this term to argue against the use of global state in software.

Today, I’ll appropriate his appropriation.  To conform to the principle of least surprise, you should avoid the appearance of such spooky action at a distance.  For instance, if I call the aforementioned hypothetical fetchAllContacts() method, you would find it surprising to find a new file generated or a random record written to the “customers” table in your database.  Touching something would inexplicably create an apparently unrelated side effect.

You do not want to subject your users to this sort of thing.  It will cause them to regard your API as mysterious and unpredictable.

Always Get Feedback

I’ll close with a generalized piece of advice.  You can follow the guidance in this post to avoid some common pitfalls regarding the principle of least surprise.  In fact, I’d argue that this will help you avoid some of the most egregious problems.

But I can’t possibly counsel you on every potential way your code could confound people.  Programming is hard.  Naming is hard.  And communicating through code is extremely hard.

So to help yourself out in general, make sure you get feedback.  You probably work with other developers.  Get their feedback on the APIs you expose, or ask them to use them and monitor how it goes.  Solicit feedback from your users as well, and invite them to collaborate with you on potential improvements.

I have no idea whatever became of that big blue binder of mine.  But big blue binders like that are a dying breed.  Take care that your APIs make life easy for your users, or they will be too.

Newest Most Voted
Inline Feedbacks
View all comments
6 years ago

Good points.

I’ve encountered astonishment to the largest degree in a “SetStatus” method…it set the status, but also updated some other records, sent emails, and realigned the stars depending on many complex conditions. And you know what? It was the source of a major bug!

Brian O\'Callaghan
Brian O\'Callaghan
6 years ago

RE: Obvious preconditions, non-obvious preconditions, and discoverability. If you have a semi-complicated domain, good error messages can help users discover the norms of interaction. Example: Construct an “Order”, then SubmitOrder(order). Uh-oh, that action failed. Look at the error message. “Order must have the ShipTo property filled in; value was blank”. I don’t know if it’s obvious or non-obvious whether an order *must* have a ShipTo, but the error message helps guide the user toward correcting the problem and discovering something about the domain. Now imagine the difference between two functionally identical APIs, except one has error messages like above, but… Read more »

6 years ago

Agree there @Brian. Where would you stand on something like

ValidatedOrder vo = order.Validate();
OrderSubmissionResponse res = vo.Submit();

or if you like –

ValidatedOrder vo = ValidateOrder(order); //vo has a token and hash …

Brian O\'Callaghan
Brian O\'Callaghan
6 years ago
Reply to  Phil

Mostly gives me the warm and fuzzies! That style seems to have a lot of currency with the FP Domain Modeling folks, and I think we’re going to see a lot more of that in the future. I definitely see the value, but I don’t know if it’s going to be applicable everywhere. But as written, I also wonder if it’s friendly enough. I suppose it sort of depends on your answer to what you think of the following: var validatedOrder = order.Validate(); var submissionResult = vo.Submit(); Some people see the “var” as evil, but others would argue it’s a… Read more »