A Couple of Lessons I Learned About API Design
One of the reasons I work on open source is to become a better programmer. Learning opportunities come from different sources, being practice the more obvious one. In the last couple of weeks I learned two important lessons about API design not by practicing my craft, but from listening to our users.
What I learned may appear as common sense by most of you. But I’d like to share anyway. I found out that common sense is not always common :)
Lesson #1: Do not sacrifice flexibility and extensibility by overprotecting your users
It started with this thread, in which Szczepan Faber, creator of Mockito, mentions that he recently found FEST’s Assertions module and it “seems very interesting.” He suggested that, in order to make the library more extensible, the class Assertions and the classes implementing assertion methods could be made non-final.
I admit I’ve been adamant to open up those classes. I rejected previous requests due to the following reasons:
- The class Assertions contains only static methods: a bunch of overloaded assertThat methods. I thought it is a “best practice” to access static methods from the declaring class only (the Eclipse compiler even has a setting to enforce this.) I honestly didn’t see the point of subclassing Assertions. I thought that if somebody needed to extend it, he or she could use composition instead.
- Classes containing actual assertion methods are also final (e.g. StringAssert) due to the lack of self-types in Java, the language. I’ll explain with an example. The assertion methods in StringAssert always return this, to facilitate method chaining
public StringAssert isNotNull() {so we can write something like
assertNotNull();
return this;
}assertThat(someString).isNotNull()
.isEqualTo("Hello World");If we need to subclass StringAssert, we need to override its methods to return the subtype, otherwise, when chaining methods, we will not see the new methods in the new subclass
@Override public MyStringAssert isNotNull() {
super.isNotNull();
return this;
}
Once again, I thought that by using composition users may have better chances of not falling into this trap.
Szczepan responded:
Hmmmm, I see point now. I would still recommend to un-finalize the assert classes… Internally, you can probably figure out some way of verifying if you didn’t forget to override (like extensive functional test suite, PMD rule or JUnit test that reflects the methods, etc.) Externally, I’d like to have an easy way to extend assertions on already handled types. Extension via custom conditions doesn’t work for me because it’s too Hamcresty and I’m doing FEST here, right? :)
Szczepan provides a use case for FEST’s API I never thought about before. Not only that, he also provides a pragmatic solution to the “problem” I was afraid of!
Soon after reading his response I realized I’ve been over-worrying about users not following certain “good practice” or forgetting to return the correct type. The worst-case scenarios are not as bad as I thought, and users of FEST’s API can easily recover from a mistake. I realized I’ve been limiting extensibility and flexibility of the API by over-protecting users.
Lesson #2: Too many options may cause confusion
In the same thread, Szczepan suggests to add the methods is and has as “humanized” aliases for satisfies, to make the API more compact and readable. The following example is taken from FEST’s wiki:
assertThat("hello").as("Greeting").satisfies(isUppercase());
// using the "is" alias:
assertThat("hello").as("Greeting").is(uppercase());
Although it makes a lot of sense (and I actually filed tasks to have
this aliases in our next release,) Ansgar Konermann, another FEST user,
brings up a very interesting point:
I like FEST assertions for its simple API. I think it is a significant plus to attract new users. We should take care not to complicate the API unnecessarily. When I was new to the FEST API, I even considered it strange to have both as() and describedAs(), which do exactly the same thing. I later understood this is a technical necessity to allow usage of FEST in Groovy. I’d love to see FEST keep its API as simple as possible.
Think about http://c2.com/xp/OnceAndOnlyOnce.html
I’d agree that satisfies/doesNotSatisfy sounds like math, not like a check of domain conditions. Nevertheless, I feel that this basic concept of a condition/predicate is absolutely valid to use when defining tests. We also kind of agree that assertThat is fine for all tests, however your business analyst would probably express this a bit different (e.g. makeSureThat, itMustAlwaysBeTheCase). If someone asked me, I’d opt not to include each and every variant which a natural language might have developed for the same meaning over time, but to convey this meaning using exactly one, carefully chosen word.
Ansgar has a valid point. Aliases can add more flexibility to an API, but it also may increase confusion to its users. Finally he adds:
In our team, the expectation of FEST’s API is: type assertThat(actual), press CTRL+Space and instantly _know_ which single method to call for the check you want to perform. No further thinking about which method to choose. Given a test intention, it should be totally obvious which method to call. Aliasing makes this harder. Don’t make me think – at least not about the methods which I need to call. When writing tests, I instead want to focus my thinking on the conditions which should be checked.
Pretty clear message. Nothing else to add.
In Conclusion
What I like the most about these two lessons I learned is that they seem to pull in opposite directions. Finding the balance between these two may help create an API that is both flexible, extensible and at the same time, does not offer too many choices that may cause confusion.
As I mentioned earlier, working on open source is a great experience. It helps us stay humble, since we are always going to find somebody with better ideas than ours.
Thanks Szczepan and Ansgar!
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)






Comments
Mast Ermnd replied on Tue, 2009/07/14 - 12:31pm
Great article, but I always fall into trap #1 about overprotection of users. Would you care to elaborate on the part where you say "Not only that, he also provides a pragmatic solution to the “problem” I was afraid of!".
Thanks!Alex Ruiz replied on Tue, 2009/07/14 - 2:10pm
in response to:
Mast Ermnd
I was referring to the PMD checking and/or tests to verify that the correct type is returned from the assertion methods.
Cheers,
-Alex
Jakob Jenkov replied on Wed, 2009/07/15 - 4:02am
I agree with #1, but I am not sure I agree with #2
Yes, too many options can be confusing, but it depends on who you are developing the API for. A noob would perhaps be confused by too many choices, but as the noob becomes a veteran, she may learn to cherish the many options. What's confusing today is simple tomorrow. I'd rather stick to #1 and go with more flexibility, and just document my way out of the many options.
Alex Ruiz replied on Wed, 2009/07/15 - 12:03pm
in response to:
Jakob Jenkov
Hi Jakob,
I completely agree with you. I didn't mean that we should avoid aliases or options, but to try to find a balance, depending on the target audience. What I learned from this exercise is to make design decisions having these two lessons in mind :)
Cheers,
-Alex
Jakob Jenkov replied on Thu, 2009/07/16 - 1:16am
Hi Alex,
Yes, I kinda got that from your posts too. That you are not following these rules as cut-in-stone religious commandments, but rather as guidelines.
BTW I wrote my own API design guidelines down in this 12 text series:
http://tutorials.jenkov.com/api-design/index.html
That series contains a lot of my experiences gained during 5-6 years of open source development of Butterfly Components among other API's.
Bruno Vernay replied on Thu, 2009/07/16 - 10:01am
Alex Ruiz replied on Thu, 2009/07/16 - 12:51pm
Thanks Jakob :)
BTW, the links you shared don't work :(
Cheers!
-Alex
Jakob Jenkov replied on Thu, 2009/07/16 - 1:11pm
Bruno Vernay replied on Thu, 2009/07/23 - 4:09am
in response to:
Jakob Jenkov
I read your API Guide and the exception tutorial: good works.
Maybe you could host it on more "social/wiki" place: this would allow comments, more robust service and maybe more printer friendly format.
Google has plenty of services: blogspot, Sites,Knol ...
By the way you could add a reference section (like you did for the Exception tutorial). Feel free to get a starting point from my own.