Tuesday, March 17, 2015

20 API Design Tips to Stop Annoying Developers

Here is my presentation at DevConTLV March 2015, slide by slide, with my accompanying talk and some notes. I don't write down what I say on the slides, not do I have a script, so the text is an approximation.

This talk was supposed to be 20 tips to stop annoying developers, but I stopped counting at 20. You're welcome to count along with the presentation and tell me how many there are at the end.

Every example in this presentation represents an actual example I've seen while documenting APIs.

Just to clarify, the title of this presentation means tips to "stop doing things that annoy developers", not "stop developers who are annoying". Which brings me to my first tip ...

Don't name your APIs with names that have ambiguous meanings. English can be tricky; if you're not a native English speaker, get one to help you review your function and parameters names.

What's confusing here? "Barks" can mean "This animal can bark" or "This animal just barked". If a developer sees this, he or she can't tell which one you mean just by looking at it. Try one of the following.

Now the action can't be misunderstood. However, ambiguity is just the start.

Take a look at the first one, "can_bark". This turned out to be a problem for me when I was documenting an API.

Typically, there are two types of APIs. In the first type, all of the business logic is in the API. In this case, the application acts as a funnel, sending information to the API and passing requests from the API back out. The application is notified about things that happened on the server, or by the user, or on the device, or in another application, or even another part of the application, and sends the information to the API. In turn, the API sends things back to the application and the application sends it back back out to the world. For this type of API, the application isn't supposed to think.

My documentation was supposed to say when to call each API function, and what to do with the information sent back from the API. The developers included a sample application that had this API "can_bark" in it. I asked the developers "When does the application call this?" They said "When the API is waiting for it". But when is that? Just look in the sample application. That's not the point, I said. The application isn't supposed to have business logic in it. The application can't know when to call anything; it just responds to external requests. If it has to decide on its own, I need to write a whole new document on how to design the application's business logic.

Essentially, the developers had drifted part of the business logic to the application, instead of keeping it in the API where it belonged. As a result, you had an API that could not be called, let alone documented, because it didn't have access to information that was inside the API.

This API would have been fine if it responded to an event or call of some kind from the API itself. In other words, it didn't have to think for itself if it just responded to a request from an external source (the API) as usual, sending the requested information back to the external source.

(Another situation arose when new information arrived about an animal ("this dog can bark") to be sent to the API. The information could be sent using an API that already existed "animalUpdate", which meant that the new API wasn't required.)

The moral of the story is: don't split your business logic. If you use the kind of API that has all the business logic, keep the business logic in the API.

Another kind of API has none of the business logic and simply responds to requests from the application. For example, a device driver writes what the application tells it to write and reads what the application tells it to read. If you drift application business logic into this API, your next customer is going to want something different from it and you're going to have to redesign the API.  So don't split your business logic.

This is me. I worked for 14 years as a system administrator and a web programmer, until I switched to technical writing over ten years ago. Between my roles as technical writer, teacher of game rules, and now a designer of APIs, one of my major skills is to turn complicated ideas or topics into very simple ones. I teach complicated games so that they can be understood in only a few minutes, take 200 page documents and turn them into 120 page documents, and take 60 page documents and turn them into 2 page tech sheets.

I have documented dozens of APIs. When I get an API to document that is overly complicated, or would require a lot of documentation (and therefore a lot of work for the integrator to understand) because it doesn't match the rest of the APIs, I go back to the developers and say, hey, rather than write complicated documentation, let's rewrite the API. That gets me into trouble with some developers, but the product managers are generally happy. The result, in some of the places that I worked, was that API functions, names, parameters, and so on went through me before they were implemented. I sat in the design process.

Which makes sense. It's not enough for a web site to work. Nowadays you hire a UX designer to ensure that your web interfaces are clean and neat and don't drive away potential users. The same should be true for your APIs. It's not enough to say "they work!" If they are complicated, ugly, or difficult to integrate, you may drive away potential integrators.

In this talk I'm first going to talk about what makes a good API in general. Then I'll give some general tips for APIs, then some specific tips for specific instances, like actions and parameters. I'll end with general tips about documentation and delivery.

What is the object of an API?

It's the same object as your UI or your product: help your customers to be awesome.  (Note that I stole this idea from a fantastic lady and speaker, Kathy Sierra.) The ultimate object is not to have an awesome product. Your product should be awesome, of course, and you can sell that in your marketing documentation. But no one wants to use your API, or read your document, or even use your product (unless it's a status symbol). They use it because they have to in order to be awesome, to do what they really want to do.

People buy a cool bicycle because it has a tungsten carbide frame, cool anti-rust paint, blah blah. But then they have to use the bike - interface with it - and they don't want to spend time trying figure out your fancy features, If the interface isn't "get on the bike and push the pedals", it means that they have to read the manual.

"Have to" is not a Good Thing. People "have to" use your APIs or "have to" read your documentation. No one "wants to" do these things (unless you're a hacker who loves systems, like me), so it's a negative experience. They do them because they have to. If they can do it easier using someone else's product, they will. If they have to use yours because you have a monopoly on some kind of feature, they will; you have them over a barrel. But you're not going to make any friends by forcing someone to do something that they "have to" do.

The object of any API is the same as the object of a technical document: to have the user/reader use it as little as possible. Ideally, not at all. If they have to use it, You've already lost something. Make it as simple, quick, and painless as possible for the user to get in and get out.

Your job in writing an API is not to sell your technology. You should not write an API that goes "start the super cool dog grooming feature". The API should be "groom the dog". Keep focus on what the user wants to do, not on what a great feature you're providing. Otherwise, your API will be bloated and loaded with all kinds of things that you think are cool but are forcing users to learn your language, showing off features that are not helping them get in and out as fast as possible. Obviously, if a user has to use a feature, you have to provide a way for him or her to use it. But keep the focus on the user, not the technology.

(Here's another slide totally stolen from Kathy Sierra.) On the left is what you promise your customer. She's going to take a cool picture while climbing a mountain. It's going to be awesome. On the right is what you deliver to the customer: technological documentation and interfaces in love with themselves, all of which she has to wade through to get to the cool picture on the left. Again, some of this is inevitable; you have to describe how the camera really works. But keep the gap as small as possible. Focus on getting the user in and out of your API as fast as possible so they can get to being cool.

 So what makes a good API?

A good API (or document) is invisible. The customer doesn't feel it or see it any more than they have to. It is so natural that it doesn't feel like they are learning or using it.

A good API (or document) is unsurprising. If there's a left, there's a right. If there's an up there's a down. If it works one way here it works the same way there. It works the way every other API works. The user doesn't have to know something different to use every functions. It works exactly as expected, so that it needs no (or almost no) documentation.

An API that presents a few good features well, unsurprising, and easy to implement is better than one with lots of complicated features that have to be learned one at a time. Complicated, hard to understand and hard to implement. It may be inevitable that you have to provide something bad or complicated for a feature that the user MUST use, but minimize this as much as possible.

In the end, an API is good when the user feels that the money he or she spent on the product was worth it.

Have someone constantly looking it over to ensure that it's all consistent and logical, even if you are working in agile. Agile development adds features here and there as they come up without worrying about how they fit together as a whole. Ronit on Inbal's team added this feature, and Itzik on Moshe's team added that feature, and they all work, so done, right? No. Ronit used one kind of parameter set and Itzik used a different kind, and now the integrator has to learn two different methodologies to integrate them, which is annoying.

Keep a master plan for your actions, objects, parameters, and values, and keep it up to date with each new addition and each release.

Because it's very hard to change an API after it's released. If your code has mistakes and discrepancies that are hidden from the user inside the code, I don't care. You can release new code and fix it. When an API is released and customers are using it, it's painful and annoying to have to rewire an applications to incorporate the new API. It's worthwhile getting the API right the first time.

How do you write a good API? Think like a customer. A customer thinks: what do I want to do? Give the customer use-cases that show what your API does, and give him APIs that do things, with useful verbs.

An R&D team has access to all kinds of dummy information at all times. They forget that, in the real world, the integrator starts with nothing. If your API takes the list of dog breeds as input, where does the customer get that list of breeds? From this other API, that requires the list of dogs. Where do they get the list of dogs? From this other API that requires the list of kennels ... It's ok to cascade API requests, just make sure the developer can easily find the input for every API.

Then make sure they can use the results you send back to them as the input for the next API. Don't send back dog names in one API and ask them for dog IDs in the next. Unless the returned information is heading straight to a UI screen or a log file, your user is not a person, it's an application; it needs to feed the results of one request into the next.

How do you end up with this, where one function is camel case and one has underscores? Well, Ronit on Inbal's team added one feature, and Itzik on Moshe's team added the other feature. "It works!" But it's annoying. Don't do it.

Same thing for the parameter order and what gets passed. The first function takes a pointer, the second takes the variable followed by a Boolean, and the third takes the Boolean followed by a literal. How did this happen? Ronit on Inbal's team added the first one and ...

Here dogs are defined by a flat set of fields with feet and so on. But cows has a two-level structure of fields with limbs, followed by feet and hands. Be consistent in how you construct your data structures.

If a name or a term appears somewhere it should mean the same thing everywhere, and the terms you use for one thing should be the same for everything. Your pet store started with only dogs, and the names used "name". They added cats, and now they use "catName". Please. Also, "bark" means one thing, for dogs yes or no, but "bark" is the same word but has a different meaning, an enumeration, when used for trees. Be consistent.

Shorter is nearly always better. Shorter words are clearer and more logical. I know you want to be helpful by adding "is" and phrases and so on like this, but it's more to read for the user and not any more clear. In this case, the test "is this an animal" is always going to appear in an "if" clause, and it reads cleaner by just using the noun "animal", which means yes or no.

But there is a limit to being short. Don't abbreviate English words. Aside from being hard to read, you might run into abbreviations that can mean more than one word. I keep running into abbreviations or acronyms used by non-native English speakers that have unintentional vulgar or ridiculous meanings.

Here the type is "dog" but the user still has to define all of these other things, like the number of "legs" and the sound a dog makes, when these should be defaults. Use defaults.

Your company decided to rebrand. From now on dogs will be "BarkBuddies". That's fine, and the new APIs use BarkBuddies, but all the old ones still use dog. Don't do that. Yes, it's a pain to have your customers move to a brand new version of the API, but that's still better than an API that uses several types of terminology for the same thing. It starts with just a few functions but it always ends up as a mess.

Get someone to review your function names, parameter names, and so on BEFORE you implement them, not a week before delivery when your R&D team is too busy to revamp all of the code to fix problems. Because they won't. They'll say they'll fix it in the next release, but by then it will be too hard and too late, and they'll be working on new features.

In my jobs, new functions went through technical writing, but it could just as easily go through QA or support, as long as it goes through someone who writes English well and will push back and ask for changes.

And of course, make sure it really does work. An API with functions that don't actually work is annoying, too.

 Here are some more specific tips.

Use a consistent naming structure for your actions. For instance, if you add a new animal, always use "add". Or "create" or "set", whatever you choose, just always make it the same. One popular method is "CRUD" which stands for create, read, update, delete. I like to use "set" and "get" because it's short and sweet. But it doesn't matter, as long as the same thing always uses the same word.

If you're using a REST API, make sure your REST commands match your actions. Don't use POST for edit or delete, or PUT for read.

Here you create an animal using a single command, but you edit each animal using multiple commands. Why? Because Itzik from Eyal's team programmed ... Here you update each item in a dog using a different command, but  edit the same things in a cat using one command for everything. Don't do that.

Verbs set, nouns get. ParagraphCapital does not make the first letter of a paragraph capital, it checks if the paragraph is capitalized. Use a verb to set the paragraph. More simply, you set a state using setState, you get the state using state or getState.

Please avoid using Booleans, because they will always, always, (almost) always change to non-Booleans in the next version. You started the pet store with dogs. Then you added cats, so some developer added isDog to some commands. But other APIs defaulted to dog, so she used isNotDog. And then elsewhere she used isCat, just to mix it up a little.

Then you start selling elephants, snakes, giraffes, turtles, and fish. How many Booleans do you need to add now? Just avoid the problem. Anytime you want to add a Boolean, assume you're going to have to make it into an enumerated list up front.

Also, avoiding Booleans prevents your integrators from having to learn many new terms, especially if they're going to localize. It's one thing to learn a list of 50 or 60 words in your API, like "type" and "breed". Don't force them to learn all the English values, too. If your integrator is Spanish, he might not use "cat", he'll use "gato". So why should he have to learn that gato "isCat"? Minimize the number of tokens and avoid putting values in your action names.

 And I don't even know what this means. If you can tell me ...

For the same reason you shouldn't embed values in actions, don't embed units. Document the units. Also, the units may change, and you don't want to have to change the action names.

Here we have the animal type. The animal name. Number of legs. Number of heads, I think, or maybe number of tails. This must be the hair color and the breed. Then height? Length?

Anyway, don't do this. There is no need to add the number of legs once you know the animal type. If beagles are generally brown, there is no need for the hair color by default.

Here we wrap the parameters in a hash, so the meaning of each field is clear and we can leave out the defaults.

Try not to use a global state. For one thing, it avoids race conditions. For another, the application now has to use business logic to figure out what state it's in, how long to wait, how often to poll for the answer, and so on. Send the result back with an event, or a socket, or an email, or what have you. Let the application be dumb.

When considering resources, you may have dozens of ID fields for your database, your UI, some other system. Don't expose them all to the integrator; only give him what he needs. Also, don't return one kind of ID from one command and then require a different ID for a different command. We want to let him cascade the output of one command into the next one. He shouldn't have to look around for the information he needs.

Like actions, don't use Boolean parameters and don't embed the values in the parameter names.

I've seen this kind of documentation. It's not helpful. Remember, your result is being sent to an application, not a person. The application has to take the result and figure out what to do next, and for that it needs the exact list to parse. If something returns an enumeration, link to the enumeration or provide all the possible values.

Enums are good. Pass the enum code, not the value. This way the integrator can localize the results. So don't pass literals. In the second example, the developer tried to be clever by avoiding literals but concatenating strings together, but that's not useful if the result is going to a different kind of UI or if the language reads right to left. Pass the code and the values and let a utility do the substitution.

Don't use literals even if you don't plan on passing them on to the user. You might enjoy parsing a literal return value, but it's dangerous. If your API returns error code 22, the next person working on the API isn't going to change the return code to 23 just because it's his birthday. So you can expect it to remain the same. On the other hand, if your application parses the return value "no dogs found", it's going to break when the API changes all references to "dog" to "BarkBuddies".

The last example was also a problem, because you may have several results to pass back, and now you have to worry about parameter order. Pass the values back in a structure and let the utility figure out how to handle it.

What do we need documented for values? The range or possible values for the value. The units. Whether there is a certain subset of range or options that is recommended, like 4 legs. The default value or option, of there is one. Any dependencies between the values, like if that is above 4 then this has to be below 3. And where the implementer can get the value; which API returns the list of options for this value, so they know how to string the APIs together.

Speaking of documentation. A big problem with a lot of documentation is the writer assumes you know everything about the product before you start. Lots of documents start with "Welcome to our system. To log in ...." That's not helpful. Take a paragraph to orient your reader by explaining what your system is, and what the user can expect to do with it. Like a mini-table of contents so they know what to look for and if it's worth reading.

Don't waste their time documenting basic concepts like classes and programming ideas. But don't assume they know what your terms or products are or do. Don't just say it's a system for managing BarkBuddies. Tell them that a BarkBuddy is a dog.

Hyperlink so that they can get more information, so you they don't have to read the same information more than once. A content generation system like doxygen, JavaDoc, Swagger, etc does this for you.

Provide examples. Some people can learn the system straight from the examples, and in fact you may be able to get what you need just by cut and pasting the examples. And include a complete list of error codes that might be sent back.

Here's a very common source of annoyance. I hate documenting delivery packages, because I should never have to. They should be self-explanatory. Also, unless the API is a web page, no R&D team ever knows how the API is delivered. Is it a zip file? A DVD? How does the user get it? R&D teams don't even know this the day before, or of, or after the delivery.

But the delivery is the very first thing that the user interacts with. Please make it nice and clean. Make all the file names and directory names self-evident. Don't include parse.h and also parseNew.h or parse2.h . Clean it up! Remove everything that is not needed: files, directories, methods, and so on. Don't make the integrator wade through all these things he doesn't need, because he WILL call support and try to figure out what to do with them. Include the localization files and anything he can customize, like enums and configuration files.

Last tips. Consider a quick start guide to get the user up and running. Consider a cookbook that the user can cut and paste. And, if it makes sense for your API, consider a wizard that will generate the code with everything already in place without errors. Just make sure the wizard is up to date with the latest code changes.

Thank you.

No comments: