To All API and Library Designers
Creating APIs and libraries can be a difficult task. There are many concerns that you have to worry about such as design consistency, correctness, function, performance, security, and the list goes on. Something that typically gets lost in the list of concerns is usability . I know you might be thinking I’m nuts because APIs and libraries don’t have a graphical user interface. Yet they do have a user interface. The users of APIs and libraries are developers, and they use the exposed functionality provided by the API. Here are a few pointers that will cause developers to shout your praises rather than curse your name:
- Error messages/Exceptions should be clear
- Design patterns should be consistent
- Documentation should be useful
I’ll spend a little more on each topic to flesh out what I mean.
Error messages should be clear
Whether your language uses exceptions or another method of notification; the user of your API needs to know if they are the cause of the problem, and more importantly what they can do to fix it. Let’s take the example of bad parameters. They happen, particularly when a user doesn’t understand what is written in the API docs or it’s just an honest mistake. When a function has a half dozen parameters, it really helps to know which parameter is causing the problem. A generic “invalid parameter exception” with no indication of which parameter isn’t very helpful. If the parameter has to be within a certain range, say so.
There’s nothing more frustrating than spending a whole day trying to figure out what you are doing wrong, searching forums for possible clues, only to turn up empty. The more you can help your users (the developer) stay focused on writing software, the better. If an exception is caused by something outside of your control, it really helps to give that information back to the developer. They may be able to fix something that your library depends on.
Design patterns should be consistent
Whether you are using formal design patterns or simply a programming idiom to help convey the intent of the API, use it the same way throughout your API. There is nothing more confusing than having exceptions to the rule. We get enough of that learning English, we don’t need it in our APIs as well. The problem with exceptions is that they raise the mental complexity of the API. Every time a developer accesses a function, they have to consider is this function operates differently (or worse: which difference applies here?).
There’s no question that designing a consistent API is very difficult. The nature of the problem is trying to find an abstraction that helps the developer solve a problem. The problem is that if you choose the wrong abstraction you will make it harder to solve the problem or write the API. It’s a delicate balance. However, if you find yourself always needing to break your abstraction in a couple of places, perhaps you chose the wrong one to begin with?
Documentation should be useful
Developers really need two types of documentation. Many API writers recognize the importance of API docs (such as JavaDocs or .Net Docs). In order for these to be useful, the person referring to the documentation needs to know more than the parameters and the name of the function. They need to know what the function does for them, and if there are related functions that handle different tasks what they are. In an API reference, that is sufficient. You just need to refresh the developer’s memory, and help point them in the best direction if there are related (but different) functions.
What usually gets lost is describing the design patterns or abstraction the API is using. It’s one thing to know what the functions are, it’s quite another to be able to put them together in the right combination. Writing down the API design approach also helps the API developers understand how they are supposed to be solving the problem. Also, by writing down the API approach, you see just how difficult it is to convey. If it is too hard to explain, it is too hard to understand, and far too hard to use. Without this foundation, the examples of how to use the API make a lot more sense.
Conclusion
The three concerns I listed here are the top frustrations I have with any given platform. Whether it’s Java’s extensive APIs, the .Net library, Ruby’s API, etc. I’ve come across violations in at least one of the three concerns. My biggest frustration at the moment has to deal with bad or confusing error messages. The worst thing you can do as an API writer is have your users play “bring me a rock” with your API. It really helps to know that parameter X is invalid because it is not within the range of Y-Z. It does not help to know that at least one parameter in a list of seven is invalid: go figure out which one and how to fix it yourself. Additionally, if the network gets dropped and you no longer have a connection to a server, that information needs to make it to the developer instead of other exceptions that are just a consequence. For example if the problem is a file permissions problem, throwing a null pointer exception only confuses the user. They will quite rightly think you don’t know what you are doing. Understandably, proper exception handling is important. Which exceptions do you expose, and which do you handle internally? When the system breaks are you doing something unexpected as a fail-over? Java’s Remote Method Invocation API failed terribly in this manner. Instead of completely failing to call the remote object, it would silently fail over to an unencrypted HTTP tunneling approach first—even if the original connection was encrypted. That’s an epic security failure. Be reasonable, and sometimes it is better to fail completely than it is to fail over to something insecure without notifying the developer in any way.
Judicious Use of Closures 9
After going back and reviewing some of my old articles, I got to thinking that my role based authorization system I outlined in this article still reflected my Java bias. I know some people approach new toys and technologies by diving in head first, and then backing off later. I personally try to think of what problems that approach can practically solve. As a result I may be a late bloomer, but the flowers are nicer.
The whole issue of why I thought it could be better is because it is still too Java/C++ based. I tried to make it more rubyesque but I stopped short. You see, in the Java/C++/C# world you think about having to query an object and then do something. This is in stark contrast to the Smalltalk and Ruby approach where you tell an object to do something for you. You can even pass in some extra steps so that you can be very happy. So what’s the difference, and does it really make a big deal? The difference is subtle, but so is elegance. Let’s consider the code I had you write to do work if a user had a particular role:
if @user.is_in_role? (:Admin) Article.find(params[:id]).destroy flash[:notice] = 'Article was successfully deleted.' end
The “is_in_role?” message is straight from Java’s HttpServletRequest interface. It does make sense from a certain point of view, but the control is inverted from what it should be. In short, we should tell the user to do something if they have a roll. Here’s how the same logic should look from the subtle shift in viewpoint:
@user.with_role :Admin do Article.find(params[:id]).destroy flash[:notice] = 'Article was successfully deleted.' end
So what’s the big deal? They both do the same thing, and they are both pretty readable. The big fat hairy deal is in perspective. The first version is interrogative, we are treating the user object like a little child and taking back control. We aren’t letting the user object do his part of the chores. The the second version is imperative, we are telling the user object what we need him to do. We trust him to make judgments like whether he can do it or not. The implementation of the method is just about the same under the covers, but the important thing to stress is who is doing what. We should provide general directions, and the objects should pull their own weight to make it happen.
Ruby is really great when it comes to closures. Every method implicitly handle closures. The trick is the yield keyword. If you want to pass a parameter to the closure, just add it on to the yield command. Here is how our implementation would look if we did the more rubyesque approach:
def with_role role
if roles.include? Role[role]
yield
end
end
That’s pretty slick when you think about it. You can make any method handle closures just by using the yield command. If we needed to pass parameters, we just tack them on after the yield command. There’s tons of applications for it, but it’s best to start off slow. After you add support for a closure to an object’s method, ask yourself if it makes life better. Is the code that matters more readable? Does it simplify life? The answers to those questions will help you walk the balance between elegant and gimmicky.
Contrast Ruby With Java
I’m cheating a little bit with referencing the current specification proposal for JSR for Closures in Java as it represents the closest thing to what you can expect here. First of all, if you use Ruby or Smalltalk you will be sorely disappointed. In a sense, Java closures still follow the same pattern in that the closure is an object—only you have to be explicit about it. Edit: Apparently I missed a very important, and good part of the new Java closure spec. I’m changing the code examples to reflect the preferred method.
First, let’s look at the Java way of solving the problem we solved with Ruby:
if ( user.isInRole("Admin") ) {
Article.find(id).destroy();
sendMessage( "Article was successfully deleted." );
}
It’s fairly readable, and it doesn’t look too bad. Again, we are treating the user object like a little child. We are asking the object about itself and assuming control. The approach has its roots in the procedural world that the C based languages came from. Now let’s look at what the new Java Closures will look like, and whether that will help things be more readable.
user.withRole("Admin") {
Article.find(id).destroy();
sendMessage( "Article was successfully deleted." );
}
There is an uglier way of declaring the closure, but since we want the code to be as elegant as possible I chose this form. Before I go into details of what is going on under the hood, we need to see the other half of the equation so we can better understand what’s going on. Here’s the implementation of the withRole method:
public void withRole(String role, {=>void} block) {
if ( roles.contains( role ) ) {
block.invoke();
}
}
Under the covers, the definition of the closure is the ugly {=>void} block. That’s telling the compiler to create an anonymous interface that has a method named invoke with no parameters and returns a void. Ok, time to take a breath. If we want to add parameters to the invoke method, we have to add them as a comma separated list in front of the => symbol. The void after the symbol is the return type. The implementation of this anonymous interface is assigned to the variable name block. There’s a bit more to it, and you can reference variables declared outside the block—but for the purposes of our conversation let’s keep it simple.
When we pass in our implementation, we use the same basic construct except we add variable names to the parameters and the implementation after the ugly symbol. We cheated in our example by not having any parameters. You might think that because the method tells what types the variables are you could skip the type declaration. You’d be dead wrong. Consider the example the spec provides (granted this is from the original specs:
// Define it
{int,int=>int} math;
// Assign it
math = {int x, int y =>
return x + y;
};
// Invoke it
assert 2 == math.invoke(1, 1);
Edit: I had to revise my initial reactions based on the input from commenters. The Java spec has come a long way. Although it’s not perfect (and what in life is?), it is a vast improvement over what we once had. The critical part of Java closures is that they are really closures. That means that you can reference variables declared outside the closure (you can’t do that with anonymous inner classes), and they enable more flexible, robust, and readable code. At least on the calling side of things. Java will never match the simplicity and elegance of Ruby, but this time it will be much improved.
The Languages Are Different, Play to Their Strengths
What I don’t want you to do is walk away with the impression that Ruby is great and Java sucks. I work with both technologies, one because I want to and the other because I’m paid to. They both have their strengths, design idioms, and code aesthetic. The important thing is to play up the strengths of the language you are using.
Just because a solution is elegant in one language doesn’t mean the same approach translates well to another. Sometimes it’s just plain impossible. Other times, the overall approach is fantastic, but the implementation will have to have subtle differences based on the language you are using. That’s OK too. My Java ActionPack is based on most of the design principles of Ruby’s action-pack (part of Rails), but they are done in a very Java way. They evolved differently, appealing to what is considered elegant and good code for the respective languages they are written for.
Edit: It appears that there are quite a few things I don’t quite get about the Java closure spec. Until we have something solid to play with, everything said is academics. Neal Grafter is the headmaster for the spec, and he should know better than I about what can and can’t be done with it.
