Atom Feed SITE FEED   ADD TO GOOGLE READER

Don't let consistency rule your APIs

Consistency in API design is great. It makes the API more predictable, easier to learn, and more maintainable. But I believe there's a difference between being consistent when writing code, and writing code to be consistent.

Consider this API:
public enum Status {
PENDING,
PREPARATION_IN_PROGRESS,
PREPARED,
DELIVERY_IN_PROGRESS,
DELIVERED;
CANCELLED,

public boolean isFinished() {
return this == CANCELLED || this == DELIVERED;
}
}
This API provides exactly what's necessary for the code that uses it.

But to be consistent, I tend to extrapolate from isFinished() and add additional methods like isStarted() and isActive(). But these methods aren't needed! They're just dead code that needs to be written, documented, tested, and maintained only for the sake of consistency.

Symmetry is a special case of extraneous consistency:
interface Stack<T> {
/**
* Adds {@code value} to the stack.
*/
void push(T value);

/**
* Removes and returns the most recently pushed value in the stack.
* @throws StackEmptyException if the stack contains no elements
*/
T pop();
}
Consistency tells me to create an exception type StackFullException and to declare that push() throws it when someone pushes more than Integer.MAX_VALUE elements. This situation extremely rare! Writing the extra code and the tricky tests isn't worth the effort.

Finally, a familiar example where consistency ruins an API:
public class InputStream {
int read() throws IOException;
int read(byte[] b) throws IOException;
void close() throws IOException;
}
When a call to read() throws an IOException, the caller probably wants to recover from the error. But when close() throws, there's not much the caller can do about it! As a result of this, I need nested try/catch blocks every time I close a stream:
  InputStream in = null;
try {
in = openInputStream();
return parse(in);
} catch(IOException e) {
throw new ApplicationException("Failed to read input", e);
} finally {
try {
if (in != null) {
in.close();
}
} catch(IOException ignored) {
}
}
A better, but less consistent, API might have close() return a boolean to indicate whether the operation was successful.

When writing APIs, be consistent and predictable. But don't be consistent just for consistency's sake - that could lead you to write too much or bad code.
Nice one.
Personally, beside consistency, I feel that some API developers want to "show off" their extended features. So, they provide interfaces with methods that nobody use!

Due to this the work of "API reduction" is thriving. After importing all bunch of OSS frameworks, the poor application developer ends up with 1000 methods after is "log." or "session.".
So, you need to wrap up everything with a small interface that hides unneeded features :-(

The in.close() example is always a good example of "What do I do now?"! In one comments of Voting for Good Exception Handling there is:
"The IOException on close can indeed happen: file has write-behind cache to network drive and network share goes away. On all other cases, it cannot.
If it does happen, the application is so likely to be dead that it might as well be thrown unchecked."