A Couple of Lessons I Learned About API Design

DZone 's Guide to

A Couple of Lessons I Learned About API Design

· Java Zone ·
Free Resource

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:

  1. 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.
  2. 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() {
    return this;
    so we can write something like

    .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() {
    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:

// using the "is" alias:
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!

From http://alexruiz.developerblogs.com/


Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}