Apr 3, 2007
Published in: Software design articles
The adoption of reflection into main-stream programming tools and languages over the last six or seven years gave developers almost telepathic powers, allowing us to instantly understand any object without having to read through 200 pages of boring manuals. Code insight, instellisense, class browser, or whatever the feature is called in your favourite IDE, started as a helpful utility but has now almost completely replaced API documentation. Most developers simply do not read supporting documents at all any more.
This puts a lot more pressure on the API and it’s authors - not only should it cover all required functionality, but it must also be intuitive to use. We must acknowledge and understand the effects of those tools in order to make our APIs better.
Some reflection tools will show the first few lines of method comments, some will not even display anything except method and parameter names. Anything longer than a line of text will probably not be read by most developers, at least when they start using the API. So the effort required to comment the code extensively, or write the accompanying documentation, is probably better spent on making the API more intuitive. We must not rely on comments to stop other developers from misusing the API – this is especially important for classes with stateful processing. Just putting a comment like “Call Clean() before executing this method” is not enough. API must take responsibility for workflow pre-conditions – if Clean() should be called anyway, then just call it internally, do not expose it at all. If the situation is not that simple, than verify preconditions in the code, and throw a runtime exception when they are not satisfied.
People want a short tutorial to start with, so they will actually read the “Hello World” example. So, that is a perfect place to put warnings, explain key design decisions or whatever messages we need to get through to an average API user. However, these must be kept short. A list with five-six bullets is the best way to pass the message. In any case, key ideas should not take more than a few paragraphs – the longer they are, the more likely they will be skipped. And there is no point in writing a document that no one will read.
Some workflow conditions, such as “This method should not execute in the main thread”, simply cannot be automated properly. Sometimes there is no default correct behaviour, or it would have a significant effect on performance. In those cases, exceptions are an excellent way to give more information - unlike comments, they cannot be easily missed. If the pre-condition is not satisfied, we can and should throw an exception – but let’s explain why the exception was thrown and give the client developer some ideas how to avoid the problem. A generic Unsupported Action exception does not help much, but one with an explanation what do to and how to do it will quickly point the developer in the right direction.
Again, too much information can have a self-defeating effect, and if the problem cannot be explained in a few lines, it’s best to keep the exception message short and provide a link to more information online.
This became my litmus test for a bad API – passing the test does not guarantee that the API is good, but failing the test guarantees that we should go back to the drawing board. People will typically look for the simplest solution, and if it takes more code to use the API properly than to make a big mistake, get ready for late-night support.
Issues with thread-safety are especially delicate, because multi-threaded problems are very hard to diagnose and debug, and correct solutions cannot be enforced with a compiler. Half a year ago, we had a heated discussion about thread-safety for plugins in our content-generator system. It made much sense to reuse plugins and not generate them on each request. One part of the team wanted to make plugins thread safe by default. The other part of the team wanted to make plugins unsafe by default, and then specify thread-safety explicitly.
The benefit of the first approach was somewhat less code in a general case for us, because we will write the first batch of plugins, and they will be thread-safe. However, once a 3rd party integrator creates a plugin with an instance variable, we would have support problems. So we chose the second way: framework can use thread-safe plugins, but that feature has to be activated on demand. That way, it takes more code to unintentionally misuse the API then to use it correctly.
In good user interfaces, basic functions are readily available, and the more powerful features are typically hiding behind an ‘advanced’ button or tab. This is one principle software API designers should really learn from UI designers - put the simple things in front, provide advanced features for power-users on demand. In our case, setThreadSafe(true) was that advanced tab.
Developers who were involved in building the API and underlying structures do not represent a typical API user – they often know too much. Most of the time, the API is there to shield the user from complexity of the underlying system, or to simplify it’s use. So having a fresh pair of eyes and hands try to actually use the interfaces after seeing just the tip of the ice-berg, can really be an eye-opening experience. Let’s give the API to someone who was not involved in development and watch closely how they will try to use it – this will give us a good idea whether we did a good job, and can the system really be used through the API without knowing all the underlying plumbing details. If they misuse it the first time, maybe we need to hide more details, or provide better defaults.
Shifting more responsibility to the side of API authors might cause some resistance - on the end, if the API author goes through the trouble of documenting intended use of his product, then it is reasonable that the responsibility of the API user should be to utilise it properly. Just closing our eyes and ignoring the issue will not make it go away - look at Swing for example. Sun’s desktop GUI framework is incredibly flexible, with layout management and event processing models that, for years, had no real competition, but it failed to take a significant part of the market. One of the main reasons for that was the perceived performance - I lost count of times when I had to explain to potential clients that Java is not slow on the desktop, but that the applications they have seen before were poorly written. And the ‘poorly written’ part, almost certainly, was because of misused threading model. Swing uses a single background thread to process all GUI updates, and by default, developers also use that thread for workflow processing, which freezes the GUI and brings up the famous Gray screen. Although the threading model is clearly documented online and in many books, a lot of developers chose to just ignore it, at least judging from the effect previous experiences with Java GUIs had on my clients.
It’s no use discussing whose responsibility was to use the API properly in that case - the client side developers should have done their homework better, but the damage was done to the product. So let’s avoid similar problems with our products by doing a bit more to make APIs easier to use.
Get practical knowledge and speed up your software delivery by participating in hands-on, interactive workshops:
Get future articles, book and conference discounts by e-mail.