Making APIs Easier to Use and Understand

How many times have you tried to use an API only to find that you had to fill in some ridiculous number of parameters with values that you had no idea about?

If you've ever done Windows programming and had to call into some of the Win32 APIs, I'm sure you've experienced this pain. Do I even need this parameter? What the heck is this value for?

Even if you aren't writing an external API, it makes a lot of sense to make it as easy as possible to use your API without having to specify a huge list of parameters and it is also a good idea to restrict the options for those parameters as much as possible.

In this post, I'm going to show you three ways you can take a complicated API and make it much more simple and easy to use.

Our offensive API method

Let's start off by taking a look at an API method that could use some cleaning up. (Examples are in C#, but the idea applies to many languages.)

Granted, this method signature doesn't look so bad, but it is not the easiest to use. You have to fill in 4 different parameters and you have to know what customerType is and have to make a decision about the numberOfTries value and what that even means.

It is pretty common to see methods like these throughout a code base. While they seem innocent enough, they end up introducing complexity, because they require the caller to do more work than is necessary to call the method and to understand more of the system than they might need to know.

In this scenario, I'm purposely not explaining what customerType or numberOfTries is, because you, as a developer trying to use this code, wouldn't know either. You would have to go and figure it out.

Let's see if we can improve this method signature a little bit.

1. Reducing parameter counts by creating objects

The first thing we can do to make this method signature a little simpler is to reduce the number of parameters it has.

The only problem is, we need all those parameters otherwise we wouldn't have them, right?

That is probably true, so what can we actually do?

Well, one really common refactoring step–which probably won't be much of a surprise to you is to reduce parameters by combining them into an object.

Now, most developers do this the wrong way. They right click and select the refactor option in their IDE and they take all the parameters and make them into a single class that gets passed in.

This is wrong, because it is simply hiding the problem and now requiring the caller of the code to first create an object that has a constructor that takes those same parameters. If you do things this way, you are just kicking the can up the stream.

Instead, you should focus on grouping related parameters together, parameters that would always be right next to each other.

In our case the parameters that make sense would be userName and password. Together those parameters make up a user's login. So, it makes sense to create a LoginInfo class that would contain those two parameters. We might even find that class has some additional functionality of its own or contains more data. But, for now we can simply refactor to make this method signature simpler.

It now requires more code to call our method, but the code is simplified and easier to understand. When someone tries to call the method and they see they need a LoginInfo object, they can very easily see that the LoginInfo object requires a userName and password and it is clear what the purpose of it is.

We've only slightly reduced the complexity by reducing the parameter count by one, but we've made the API more purposeful and clear.

2. Using enumerations to limit choices

We aren't done yet though. We still can do quite a bit more to improve this simple API.

The next big issue with the API is that the customerType parameter is very vague. We can't immediately deduce what it means and what value should be put in there. Is there a restricted list of values? Is it free form? Does it even matter? Or can I just put a blank string there?

We can solve this issue by restricting the possible values using an enumeration. In this example, we'll assume the customerType can be one of 3 different values. We were requiring the caller to type the string representation of those values manually–and not make a mistake–but, we can improve the experience–and reduce the error rate–by limiting the choices to just what is valid.

Again, a fairly simple change, but a powerful one, because it really has reduced the complexity for the caller. Now, the caller doesn't have to figure out what should go in that string parameter for customerType and can instead select from one of three possible options. By making this change, we've also enabled the caller to utilize the auto-complete feature of the IDE to assist in making a choice and we've greatly reduced the chance of error from a typo, since we are now validating the values at compile time instead of run time.

For this reason, anytime you can restrict choices for a parameter in a method, you should. Enumerations are the easiest way, but you can utilize other techniques as well.

For example, suppose you had a method that took a parameter maximumAge. You could make this an int, but, in most cases you would be better served by creating an Age class that had its own validation to make sure the actual age was an integer between 0 and say, 130. You wouldn't catch errors at compile time, but you'd be greatly restricting the possible values and you'd make the intent of the parameter even more clear through the name of its type.

3. Using default values to reduce required parameters

In many cases there are reasonable defaults that can be provided for method parameters in an API. Whenever possible, it makes sense to use default values and optional parameters to further reduce complexity and only require the caller of an API to worry about what is important to them.

If your API is mostly used in one way or there is a reasonable default, provide the default and make the parameters optional. Callers who need extra functionality can always override the defaults.

In our example, we can get rid of the need to specify a customerType and the numberOfTries by providing a reasonable default for both.

Now, calling our method is dead simple for most cases. We can just provide the loginInfo and if we need more control, we can override the customerType or the numberOfTries.

It's not rocket science

What we did here was pretty simple, but don't let the simplicity fool you. We have made small–and arguably obvious–changes, but we've greatly reduced the mental burden for the caller of this method.

We would see a much larger cumulative benefit from applying these techniques to an entire API or code base. When you start restricting choices and grouping related data into objects instead of raw parameters, you end up with a synergistic effect in your code, because you are able to know more about the data that is being passed around.

For example, changing our customerType to an enumeration made it easier to call the method, but it also makes our logic inside the method simpler, because we don't have to check for invalid values. Anytime we can rely on data being within certain bounds, we can simplify the logic for handling that data.