Win Your Copy: Practical API Design: Confessions of a Java Framework Architect
Buy it now
One Minute Bottom Line
If you write code then you create APIs and this book is for you. The author, Jaroslav Tulach, is the founder and initial architect for the popular NetBeans IDE. In this book he shares the hard lessons learned during ten years of designing APIs that are used by developers all over the world.
And if others don’t use your APIs, then it might be that you need this book even more.
“Practical API Design” is divided into three parts:
In part one, Jaroslav describes the theories underlying his approach to API design and his reasons for writing “another design book.” This section is reasonably brief and introduces the main concepts underpinning the rest of the book.
In part two Jaroslav, draws on his decade of experience and expounds on what works and what to avoid when designing frameworks and APIs for public consumption.
Part three is where the rubber meets the road and Jaroslav shows how the theories and ideas from part one can be applied in daily life to achieve the practical application of the rules as described in part two.
PART :1 Theory and Justification
CHAPTER 1: The Art of Building Modern Software
This is a bit of a history lesson, looking at how the approach to developing software has evolved over the years and the parallels with how people learned to understand the real world. Out of this, comes the idea of cluelessness and the benefits of being able to ignore how most things work and just use them.
Expanding on the idea, Tulach advances the idea of designing APIs in such a way as to promote “selective cluelessness” for the users. Selective cluelessness means the users can be clueless about how the API works and still use it properly.
CHAPTER 2: The Motivation to Create an API
Since taking the time to design a worthwhile API isn’t cheap or easy, there must be some benefit to doing it. This chapter looks at some of the motivational reasons for doing so which includes, supporting distributed development teams, modularizing the application, and communicating the overall design of the system.
The chapter ends with a nod to reality: while it is easy to create the first version of an API, it’s also likely that it will need to evolve for any number of reasons. Therefore, it makes sense to design your APIs with that in mind so your users can be clueless about the change - unless they’re actually using a new feature.
CHAPTER 3: Determining What Makes a Good API
Given the focus on supporting “clueless” use of APIs, it’s no surprise that Jaroslov considers this a key feature of a good API. The surprise is that he doesn’t limit his definition of API to classes, methods, and the Javadoc that describes them; he includes configuration files, environment variables, command-line options, text messages (like those from a Java object’s toString() method), and protocols (like the one used between a Subversion server and client) as well.
Jaroslov suggest that in measuring the quality of an API we look at things that can be measured to some degree. His qualities include comprehensibility, consistency, and discoverability (how users figure out it exists and what it can do). He also reminds us that “simple tasks should be easy” - something that seems to have been forgotten by more than a few API designers - and that the API user is making an investment in time and money, and they don’t want to have to start over again just because we didn’t want to make the effort to evolve the API in a backward-compatible way.
CHAPTER 4: Ever-Changing Targets
Chapter 4 starts by harkening back Chapter 2 and the statement that the first version is always easy, with an addendum - the first version is never perfect. Which means, of course, that it will need to change; either because the requirements have changed, or because the users found a bug (real or imagined) that needs to be addressed.
There are two main ways to address this issue; freeze the existing API and create a new one to replace it, or modify the existing API and provide some form of backward compatability. The bulk of this chapter discusses the plusses and minuses of each approach, and ends with an argument stongly suggesting the incremental improvement approach.
PART 2: Practical Design
CHAPTER 5: Do Not Expose More Than You Want
This chapter starts with the traditional advice to an API designer (“don’t expose more than you have must”) but adds the explanation that the public API is a promise to the users, and the designer and developers of the API must keep that promise by maintaining and evolving the API in a backward-compatible way or risk losing those users.
The rest of the chapter gives some advice on how to minimize the promise your API makes. For example: a method is better than a field, a factory is better than a constructor; make everything final, and don’t expose deep hierarchies.
CHAPTER 6: Code Against Interfaces, Not Implementations
While this bit of advice is at least as old as C++ and may be older, Jaroslav takes it the next step and looks at what it really means for Java. In keeping with his expanded definition of an API, he suggests that the idea of “coding to an interface” isn’t limited to usage of the Java keyword, but can also refer to abstract and concrete classes as well. In the latter cases, it means ignoring the current behavior of the object and paying attention to the intent of the methods.
CHAPTER 7: Use Modular Architecture
Jaroslav recognies that in order to create the complex applications and system we do, it is often useful to have pre-built libraries and components (i.e. modules) that can be easily used as building blocks, rather than having to write everything from scratch every time.
This chapter describes different ways of approaching that modularity and some of the common mechanisms (like dependency injection) that make it possible.
CHAPTER 8: Separate APIs for Clients and Providers
While it seems obvious once you think about it, few API designers seem to realize that there might be differences between APIs intended for users and APIs intended for developers or implementers. In other words, you need to think differently when designing an API you are exposing for other developers to use in their code, and the APIs you want other developers to implement so you can use them from your code. This difference also extends to how the APIs can, and should, evolve.
There’s a nice example using the java.io.Writer class and exploring various ways of implementing the the apend() method that was added to it for Java 5. The example walks you through various possible solutions and the benefits and pitfalls surrounding them
CHAPTER 9: Keep Testability in Mind
Jaroslav warns that if you don’t make it easy (or at least possible) for your users to write tests that use your API, they will probably find a replacement. He then points out things you, as an API designer, can do to make it easier for your users. For example, make it easy for users to create mock objects for important items in your API by exposing them as interfaces.
Another possibility is to provide a “testing API” which is only intended for writing tests. A good testing API could be a mock object implementation of the actual API, as well as utilities that expose some of the details of the production code’s implementation.
The final suggestion is that if you are expecting other implementations of the API then you should consider providing a Test Compatability Kit that people can run against an implementation to verify that it behaves in accordance with the specifications for the API. Doing so means a lot of work, but it also means that implementors and users can be sure that a particuar implementation of your API is correct, which increases the likelihood of their using it.
CHAPTER 10: Cooperating with Other APIs
Jaroslav explores the issues that arise when you must use someone else’s code in yours. Whether you expose the foreign API or not, the simple fact that you are depending on an external project increases the complexity for you and your users. Jaroslav spends over 20 pages looking at various problems and solutions related to this issue.
CHAPTER 11:Runtime Aspects of APIs
Here’s where things get really interesting. I’ve read about most of the ideas covered in the previous chapters before, but in this chapter Jaroslav looks at designing APIs so that everything not only compiles and links properly, but so that it runs properly even when modules are swapped out for newer ones. Jaroslav takes the opportunity here to promote the idea that you can increase the reliability of your code by making it easier for the user to be “selectively clueless” about how it works.
This chapter covers complex topics like multi-threading and the avoidance of deadlocks, selecting a reasonable threading model for your API and ways of testing code for deadlocks and race conditions. It also looks at the problems with re-entrant code, and how to use logging ot help you find problems and figure out what’s actually going on.
The chapter ends with a look at memory management in Java. Yes it is possible to have memory leaks in Java, but beyond that you can have bizarre behavior like getting a NullPointerException on a static field that you know was initialized because of the way the garbage collector and class loaders interact.
CHAPTER 12: Declarative Programming
One way to reduce the problems described in the previous chapter is to remove the code that’s causing the problem, and in this chapter Jaroslav describes a means of doing just that. For the purposes of this chapter, Jaroslav defines “declarative programming” as having your users “declare” what they want to happen instead of describing it step-by-step.
This sounded a little odd to me at first, but then I thought about how using the @Stateless annotaion to declare that a class should become a stateless session bean resulted in all the code generation needed to make it so.
The rest of the chapter looks at the benefits of making an APIs state and behaviors immutable, and different methods of keeping documents (like configuration files) backward (and sometimes forward) compatible.
PART 3: Daily Life
CHAPTER 13: Extreme Advice Considered Harmful
Interestingly enough Jaroslav starts out Part 3 by taking an entire chapter to point out that blindly following the advice given in Part 2 is not a good idea. He takes some of the suggestions like “An API had to be simple” and “An API must be 100% compatible” and show haw trying to apply them in the wrong context leads to problems and poor API design.
CHAPTER 14: Paradoxes of API Design
In this chapter, Jaroslav discusses things he’s found to be true for API design that seem to contradict what we’ve learned doing other types of design and development. This seems to be true because most types of development move between two states; development, and sustaining. Each release of the system, starts in development mode where it’s OK for you to make major changes to how things work and what features are available. Once the release is made public, that version goes into sustaining mode where ensuring that all changes and bug fixes are being backward compatible (with the now current release) is the most important things. Of course, while some people are sustaining the current version, others are busy preparing the next one - and they’re back in development mode.
With APIs that is not the case. Once the first release is made, the API stays in sustaining mode until it is retired or (very rarely) replaced with a non-compatible version.
CHAPTER 15: Evolving the API Universe
Once your API is public, or has enough other users that it may as well be public, you have an obligation to maintain it. Even if the API and/or the implementation have major problems. That’s where evolution comes in.
This chapter looks at different techniques you can use when evolving your API or its implementation that will minimize the pain such changes will inflict on your users.
CHAPTER 16: Teamwork
Jaroslav discusses some of the tools that have proven themselves useful to the NetBeans team as their project has become more successful and their team has grown from a small collocated group to a large distributed organization.
CHAPTER 17: Using Games to Improve API Design Skills
In this chapter Jaroslav explains how to use an API Design Fest to give groups of developers a quick bit of experience that will help them to be better API designers, and a common set of experiences so that it is easier for them to communicate about API design and implementation.
Included is a description of how to host your own API Design Fest, and examples from the first API Design Fest with comments about the submissions.
CHAPTER 18: Extensible Visitor Pattern Case Study
This chapter looks at another way to gain API design skills; find an interesting problem, think up possible solutions then examine the pros and cons of each. Jaroslav walks us through an example using a proposed solution for evolving one of the JDK APIs for Java 6.
CHAPTER 19: End-of-Life Procedures
The bulk of this book recommends ensuring that any changes made to your API are backward compatible so that any code using it will continue to work properly. This chapter looks at ways of allowing you to remove fields, methods, classes, and even whole packages from your APIs without breaking the promise of backward compatibility.
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)