Introduction
Whether you know it or not, you are an API (Application Programmer Interface) designer. Every time you create a new class or enhance an existing one, the non-private methods and properties create, or add to, an API. So it makes sense, I think, to talk about what it takes to create a good API.
Unfortunately, there’s not enough room in a single article to cover all the topics involved in good API design. So, I’m going to briefly describe three of them, point you to some other resources, and ask you to contribute your ideas and questions on the topic.
Make it easy to use your API correctly
A good API should be easy to use correctly. That means, it should be easy to do the right thing, and difficult, if not impossible, to use it incorrectly.
I think it’s hard to argue against this point, but what does “easy to use correctly” really mean? As an example, let's compare how easy it is to determine whether a supplied date is Valentine’s Day using the Date
class from the Java SDK and the DateTime
class from the Joda Time library.
Here’s a first attempt using the java.util.Date
class:
public boolean isValentinesDay(java.util.Date date) {
return((date.getMonth() == 2) && (date.getDay() == 14));
}
This looks pretty clean and wasn’t difficult to code. Unfortunately, it’s wrong. Let’s skip over the fact that the first methods you’d expect to use were deprecated years ago and should no longer be used, because there are more insidious problems. The test using getMonth()
is wrong because Date
uses a zero-based count for months, so we need to compare the result of getMonth()
with 1 to test for February. The other problem is that getDay()
doesn’t return the day of the month, it returns the day of the week, where Sunday = ‘0’ and Saturday = ‘6’. The correct method to call to retrieve the day of the month is getDate()
, though it seems to me that calling getDate()
on a Date
objects should return itself.
Now, let’s see what it looks like using the Joda Time library:
public boolean isValentinesDay(org.joda.time.DateTime date) {
return((date.getMonthOfYear() == 2) && (date.getDayOfMonth() == 14));
}
This code also looks clean and easy to use. It’s involves a little more typing than the java.util.Date example, but this has the advantage of being correct. In Joda Time, the months go from 1 to 12, but if you want to make the month test even clearer, you can use DateTimeConstants.FEBRUARY
instead of the magic number 2.
In this example, I’d say Joda Time does a much better job of making it easy to use the API correctly than the Java SDK does.
Make it hard to use your API incorrectly
What does it mean to prevent people from doing something wrong with the API? It depends on what the API is supposed to do.
For a Date
class, it could mean not allowing the user to create an invalid date. So, when a user tries to set the day of the month to an invalid value (like February 30th, 2009), you have a choice: you can throw an exception, or do what the java.util.Date
class does and roll the date to March 2nd.
While either solution keeps the user from setting an invalid date, silently fixing things can be dangerous because the user may not realize they’re doing anything wrong. Throwing a checked exception is sometimes seen as being “unfriendly”, but it is a way to explicitly tell the user that something is wrong so they have the opportunity to correct the problem before continuing.
Design your API with the user in mind
One of the best ways to determine what methods and properties your API should provide is to write some code that exercises the API. Note: You don’t have to write Unit Tests, though doing so is a dandy way to design your API and prove that it works at the same time.
As an example of why this is important, let me tell you about some legacy code I recently saw. The API included a method named getQueueCount()
that accepts a string containing the name of a JMS queue and returns a count of the messages available in that queue. Most of the code inside the method is used to do a JNDI lookup and get an instance of the named queue, while just a few lines are used to get and return the message count.
The thing that makes this so bad is that in every place getQueueCount()
is called, the calling class already has a reference to the queue, and has to call the getQueueName()
method so it can get the string required by the getQueueCount()
method.
If the API designer had written some client code, they would probably have noticed this pattern and could have refactored the getQueueCount()
method to accept a reference to the queue. This would’ve made it easier on the client (no call to getQueueName()
) and on the developer coding the getQueueCount()
method, because they wouldn’t need to lookup the queue and retrieve an instance of it; they could just get the message count and return it.
Books, Articles, etc.
Now, it’s your turn
Do you have a hint, tip, suggestion, question, or warning concerning API design? If so, please add it to the comments so we can all benefit.
History
- April 29th, 2009: Article submitted.