Keeping it simple

By writing client code first we can avoid the most embarrassing and bothersome API design mistakes. This approach also leads to simpler APIs. Nevertheless, APIs grow in size and complexity as the number of use cases grows. Many developers aren’t prepared to spend extra effort to keep APIs simple because they believe that API size and complexity are inextricably linked. But this cannot be entirely true: both the core Java API and the .Net API are quite large, yet we don’t find them particularly hard to use. While size and complexity are certainly related, they are not the same. There are techniques to make large APIs simple and easy to use.

We propose an easy-to-use measure of API complexity: count all named API constructs used in a scenario – all types, methods, enumerated values, constants, and exceptions – and subtract this number from 21. The higher the result, the simpler the scenario is. If the result turns negative, it is a sign to start thinking about simplifying the API. Let’s apply this method to assess a sample Java code written using our (internal) AuthenticationProvider interface, which prints a list of names and phone numbers from the corporate directory:

try {
   AuthenticationProvider/*20*/ provider =
      new LocalAuthenticationProvider/*19*/();
   SearchCriteria/*18*/ criteria = new SearchCriteria/*17*/(EntityName/*16*/.USER/*15*/);
   criteria.addPropertyToFetch/*14*/(PropertyName/*13*/.COMMON_NAME/*12*/);
   criteria.addPropertyToFetch(PropertyName.PHONE/*11*/);
   criteria.addPropertyToMatch/*10*/(PropertyName.DEPARTMENT/*9*/, "R&D");
   criteria.addPropertyToMatch(PropertyName.LOCATION/*8*/, "Waterloo");
   criteria.setSortProperty/*7*/(PropertyName.COMMON_NAME);
   ProfileIterator/*6*/ iterator = provider.search/*5*/(criteria);
   while(iterator.hasNext()/*4*/){
      Profile/*3*/ profile = iterator.next()/*2*/;
      Property/*1*/ commonName =
         profile.getProperty/*0*/(PropertyName.COMMON_NAME);
      Property phone = profile.getProperty(PropertyName.PHONE);
      System.out.println(commonName.getValue()/*-1*/, “  ”, phone.getValue());
   }
}
catch(AuthenticationProviderException/*-2*/  e) {
}

We count each concept only once and we do not count the standard language and library features. The result of -2 shows that the API could use some improvements.

Accidental complexity

Let’s see what we can do. If we replace our custom ProfileIterator with the standard Java Iterator<Profile>, the “simplicity score” increases from -2 to 1. If we use the standard NullPointerException, InvalidArgumentException, InvalidStateException and RemoteException instead of our own AuthenticationProviderException, the “simplicity score” becomes 2. If we add a direct method like

public String Profile.getValue(PropertyName);

we eliminate one type (Property) and one method call (getValue), raising the “simplicity score” to 4. With only a few simple design changes we managed to reduce the complexity of the scenario to an acceptable level.

We can use this measure of complexity to explain certain API design rules and best practices. For example, asking callers to extend classes or implement interfaces is generally discouraged. Why? Because the caller may need to implement/override several methods for a single scenario, lowering the “simplicity score”. Similarly, if we measure the complexity of the design patterns from the “Gang of four” book, we get low numbers, which is one reason why these patterns aren’t recommended in APIs.

Providing alternate implementations for existing interfaces is an obvious, yet effective, technique for adding functionality to APIs without increasing their complexity. The Java Collection Framework is a good example: instead of providing different types for mutable, immutable, re-entrant and non re-entrant collections, it provides alternate implementations. We can turn a regular Set implementation into a re-entrant Set implementation by calling

public static<T> Set<T> Collections.synchronizedSet(Set<T> s)

Instead of an entire new type, this design only requires an additional method in the API.

When we apply design techniques like the ones above, we minimize accidental complexity. To put it simply, accidental complexity occurs when usage scenarios are more complex than necessary. This happens either because we make incorrect assumptions about what features we need or because we accept feature requests too easily.

Feature requests are often a combination of actual requirements and specific API design suggestions. While we must consider the requirements, we shouldn’t feel compelled to accept the design suggestions. API users are selfish; they ask for the simplest solution for themselves, but not necessarily have the interests of other users at heart. One user’s favorite feature becomes another user’s nightmare if we are not careful. The best way to handle such requests is to accept the requirements, express them as use cases, and then find a design which supports them without adding much complexity to unrelated scenarios. We should follow Joshua Bloch’s advice: “You can’t please everyone so aim to displease everyone equally”.

Essential complexity

Accidental complexity is the easy part of the problem. As we add more and more use cases, the complexity of APIs grows, and no design technique can completely prevent this. This is called essential complexity.

The easiest way to reduce essential complexity is by leaving functionality out. As Joshua Bloch says, “They [extreme programming proponents] do advocate leaving out the bells, whistles, and features you don’t need and add them later, if a real need is demonstrated. And that’s incredibly important, because you can always add a feature, but you can never take it out. Once a feature is there, you can’t say, sorry, we screwed up, we want to take it out because other code now depends on it. People will scream. So, when in doubt, leave it out.” When we designed the authentication service used in the example above, we decided that it will not provide related services like authorization, session management, or storage for user accounts. We received some complaints about this decision over the years, but it also enabled us to keep our API reasonably simple.

We must accept that giving up something valuable is the only way to make a use case simpler in the presence of essential complexity. In the previous example we gave up functionality in exchange for simplicity. When this is not possible, we may try giving up some flexibility by deliberately limiting the supported usage scenarios. If we know how the API will be used, we can work with sensible defaults instead of keeping every option open.

For example, an XML processing library has many options for formatting white space in XML output: whether to insert line feeds and where, whether to indent nested XML elements and by how much, whether to use spaces or tabs, and so on. These options exist because the designer of the XML library didn’t know exactly how the library will be used. But if we are using XML to store configuration information, we know that the files are small, there are no deeply nested XML elements, and the administrator views and edits the file using a simple text editor. Thus, we can choose the XML formatting options ourselves and avoid exposing them through the API.

Another possibility is to trade some control for simplicity. Coarse-grained APIs have more functionality per method call and are simpler to use, but offer less control to the caller. Finer levels of granularity give more control at the expense of many more method calls. When APIs are getting complex, we can give up some of this control and increase the granularity of APIs. For example, Data Transfer Object arguments let methods do more work because they carry a lot of information. On the other hand, Data Transfer Objects themselves are simple data structures, having no methods of their own.

Divide and conquer

If, despite our best efforts, uncomfortable levels of complexity remain in our APIs, we shouldn’t get entirely discouraged. People have been dealing with complex problems for a very long time and have devised practical methods of coping with them. All these methods are applications of the same old “divide and conquer” principle.

We can help our users cope with complexity by organizing our APIs into smaller, more manageable parts. For example, a complex multi-media asset management API can be divided into several functional areas, such as basic asset management, metadata management, search, video processing, and so on. With only 24 methods, the core AssetServices interface handles all essential operations like asset checkout, retrieval, renaming and deletion. MetadataServices needs only 7 methods for saving and retrieving all extensible descriptive asset metadata. The 16 methods of AssetSearchServices interface handle all search functionality. These interfaces are in separate namespaces, each with a single entry point highlighted by the use of a consistent naming pattern. The functional areas are reasonably self-contained. Common scenarios can be realized without referencing more than two functional areas. It is also easy to understand how the various parts are tied together by the use of common asset identifiers.

Dividing APIs into functional areas is just one way of organizing them. We can also separate core features from the advanced functionality or higher level calls from lower-level, more detail-oriented ones. No matter how we do it, we are applying the principles of high cohesion and low coupling of modular software design. Refactoring APIs does not remove any functionality, just organizes it into smaller, more manageable units.

We can also remove complexity from common use cases by designing extension hooks into APIs. Common use cases are covered by default, built-in behavior, while fringe cases by plugging in custom logic. We keep the common use cases simple at the expense of making less common ones more complex, a reasonable tradeoff in many situations. For example, a batch processing system may define an Agent to handle compound jobs, and a Distributor to load balance jobs within a cluster of servers. The default Agent and Distributor implementations are designed to work for a well-defined set of common uses cases. For more advanced scenarios, callers can replace the default behavior by registering their own custom Agent or Distributor implementation. While writing custom Agents and Distributors is a complex task, it is rarely needed.

Conclusion

Keeping APIs simple requires effort:

  • eliminate accidental complexity by choosing the best available design options
  • limit essential complexity by tightly controlling scope, preventing feature creep, and giving up some flexibility or control
  • make complexity manageable by organizing APIs into units of high cohesion and low coupling
  • consider extension hooks to support advanced scenarios without impacting common ones

It is effort well spent, as simplicity is highly desirable in APIs.

 

Creative Commons Licence
This work is licensed under a Creative Commons Attribution-ShareAlike 2.5 Canada License.

Advertisements

Considering the perspective of the caller

Regular software design produces mediocre APIs because focus on implementation hurts APIs in many different ways. We will illustrate how with an internal Java API to avoid singling out any well-known public APIs. We immediately notice the complexity. The class ContentInstance, for example, appears 5 levels deep in the class hierarchy, and implements 4 additional interfaces (not counting the standard Serializable interface):

com.company.service.javabean
Class ContentInstance

java.lang.Object
  extended by com.company.service.common.DataObject
      extended by com.company.service.javabean.ManagedObject
          extended by com.company.service.javabean.ExtensibleObject
              extended by com.company.service.javabean.ContentItem
			extended by com.company.service.javabean.ContentInstance

All Implemented Interfaces:
    IAttributedObject, IChannelAssociate, IPersistable, IRelatedAttribute, java.io.Serializable

ContentInstance itself defines (or overrides) 33 methods, which is reasonable, but it also inherits a whopping 79 methods from ManagedObject, 8 methods from ExtensibleObject and 7 methods from DataObject for a grand total of over one hundred methods. That’s a lot of methods in a single class! We agree that complex problems require complex solutions and we should have no problem seeing similar complexity in a (hidden) implementation. In APIs, however, we greatly value simplicity. We don’t like spending time browsing through dozens of classes and hundreds of methods. We like it when ContentInstance is always ContentInstance and not ManagedObject or IChannelAssociate depending on the context, which tends to happen a lot when using such complex inheritance hierarchies. We are glad that someone else did the implementation work for us, but we don’t feel the need to understand how they did it. We focus on what we are implementing when using APIs, and frankly, we don’t have much time for anything else. The better the API manages to hide implementation details from us, the more we appreciate it.

Excessive abstraction is another problem which arises from implementation-focused API design. DataObject, ManagedObject, ExtensibleObject, ContentItem and ContentInstance are all pure design abstractions with no corresponding real-world objects or concepts we could immediately relate to. What’s the difference between DataObject and ManagedObject or between ContentItem and ContentInstance? We need to understand the whole API before we can understand its parts, a daunting task with a large API. We are happy to acknowledge that no complex problem can be solved without powerful abstractions. On the other hand, we need to confess that we find it difficult to understand someone else’s abstract concepts, because for this we must think like that other person. We wish the other person thought more like us instead, in familiar concepts like Document, Folder, Project or User.

Complex and counter-intuitive usage patterns are a third annoyance of implementation-focused design.  Read a programmer’s recollection of trying to figure out how to create a new instance of the ContentInstance class: “At first, I didn’t expect that ContentInstance can be instantiated because I found no constructor and no factory method in the class definition. Only after further investigation did I discover the newInstance() factory method ContentInstance inherits from the abstract super class ManagedObject. I was confused by the abstract base class declaring a factory method while the concrete class did not. Eventually, I learned from the documentation that using the ContentInstance and ContentType classes is similar to using Object and Class in the Java reflection API. The correct way of instantiating a ContentInstance was by calling ContentType.newInstance(), the inherited static newInstance() method proving to be a bit of a red herring. While the analogy with the Java Reflection API certainly helped, I started wondering if writing a program using this API would be just as awkward as writing an entire Java program using the reflection API…”

There are many other signs of implementation-focused design in the API. An out-of-process call is evident when IManagedObjectRef.getManagedObject() method throws a java.rmi.RemoteException. The object-relational mapping layer (Castor) is revealed when the class AttributeData inherits from org.exolab.castor.jdo.TimeStampable. Internal caching is obvious from methods like ContentType.clearCache(), while the underlying database schema is visible and accessible through methods like AttributeDefinitionData.getColumn(). With so many implementation details spilling out, we are left to wonder which part of our code will break when we upgrade to the next version of the API.

Designed for use versus designed to implement

The above issues may seem hard to avoid, but in practice they are not. While doing API usability tests at Microsoft, Jeffrey Stylos and his colleagues discovered a surprisingly easy way to do it. It happened almost by accident: on a few occasions, they asked the developers to solve simple programming tasks without giving them any specific APIs to use, instead they allowed them to make up the APIs they wanted to use. They were surprised by what they saw: when asked to send a simple text message using an unspecified messaging interface, all the developers wrote:

TextMessage msg = new TextMessage();

and not a single one of them wrote

MessageFactory factory = (MessageFactory) DirContext.lookup(“MessageFactory”);
TextMessage msg = factory.createTextMessage();

Given the opportunity to design their own graphics API, none of the developers wanted to write

Image.draw(false);

instead, they wanted to use two distinct methods, similar to these:

Image.overlay();  //draw over previous image
Image.draw();       //erase previous image before drawing

Boolean method parameters hardly ever figured in any of the APIs which developers were asking for. None of the developers thought they would need to do complex initialization steps; instead they assumed that the API will work right out-of-the box. Few of them expected that they will be required to extend classes, to implement interfaces, or to catch and handle exceptions. None of them wrote more than a few lines of code for the basic scenarios they were asked to implement. They just assumed that the API will take care of the details and hide the complexity from them.

The APIs the developers designed for themselves to use and someone else to implement were thus very different from the APIs they would design for themselves to implement and someone else to use. The conclusion is that the APIs we design are heavily influenced by our point of view. Let’s call these the caller’s point of view and the implementer’s point of view. Since the API is implemented only once but used many times, it should be clear that the caller’s point of view is dominant in API design.

Write client code first

So how do we design APIs from the caller point of view? By doing the exact same thing the developers were asked to do in the above experiment: writing the client code first. Not just once, but separately for every core usage scenario we want the API to cover. As we do this, repeating API usage patterns will emerge, as well as the types and methods which we need to provide. It helps when the developer writing these usage scenarios is not the same as the one implementing the API, to prevent any accidental “implementation bias”. It also helps if more than one person contributes code scenarios, so that personal preferences and programming style won’t have an undue influence on the API.

It is very important to point out that we are advocating writing real code for solving real problems as use cases, not pretend or throw-away code. The written code should be constantly updated and maintained as the API evolves and it should work correctly when the API is finally implemented. We don’t consider this wasted time, as this code can be reused for samples in the API documentation and as part of the API test suite. We should be skeptical about any API for which code samples are not readily available.

If an application has a graphical user interface, it is very common practice to model the API after the GUI, with method calls corresponding roughly to user actions, method parameters to user input, and results to what is displayed on the screen. This correctly reflects the user’s perspective and has nothing to do with the implementation, right? Well, the caller (programmer) and the user are not the same; they have drastically different needs. Issues with such APIs include:  insistence to log in with user name and password even when writing code for  unsupervised batch processes, excessive dependence on exception handling (the result of reusing existing input validation and error reporting logic), over-abundance of basic data types in method signatures (especially the string data type), data structures that have the same fields as forms displayed on the user’s screen, and so on. Such APIs are perhaps useful for developing alternative GUIs, but are less suitable for other scenarios.

The true test of our commitment to the “consider the caller perspective” guideline comes when we need to provide programmatic access to existing functionality. The implementation already exists. What are we going to do? Start coding scenarios and design an API from scratch? Or do we succumb to temptation, document the implementation we already have and call it an API? After all, any other API will require bridging, will introduce new bugs and may cause some performance problems. Why waste time on it?

If so many embarrassing (for the designer) and irritating (for the user) API design mistakes can be easily avoided by considering the viewpoint of the caller and writing the client code first, why is this not a common practice? Honestly, we don’t know the answer. The necessary time and effort certainly plays a role. Ultimately, just like in the case of User Experience Design, API Design is either part of an organization’s culture or it isn’t. One thing is for sure: considering the viewpoint of the caller is an essential part of any disciplined API design process.

Creative Commons Licence
This work is licensed under a Creative Commons Attribution-ShareAlike 2.5 Canada License.

Why developer-friendliness is central to API design

Today, APIs play a bigger role in software development than ever before. The evolution of computing has been dominated by ever-increasing levels of abstraction; the use of higher-level languages, of course, but also the development of platforms, libraries, and frameworks. Professor Douglass C. Smith claims the progression of this second category far outpaced the development of programming languages.  Developers are also noticing that difficulty has shifted from designing algorithms and data structures to choosing, learning, and using an ever-growing set of APIs. Yet, many developers may be surprised to learn just how much effort goes into designing these APIs.

Microsoft took API design very seriously since the beginning of Windows in the early 80s, understanding that the success of a platform depends on the number of developers it attracts (watch Steve Ballmer yelling “developers, developers, developers” on stage). A more recent example of Microsoft’s API design efforts is showcased in the extensive guidelines developed for the design of the huge .Net Framework API. And Microsoft is not alone in these efforts. Sun (now Oracle) has very similar guidelines for the APIs that form the Java SDK. In a 2002 interview, Joshua Bloch, former architect in the Core Java Platform Group (currently with Google), talks about Sun’s approach to API design. In yet another example, SAP AG hired a team of researchers from Carnegie Mellon University to help design a set of new web services interfaces.

Taking API design seriously isn’t restricted to the large software vendors. Volunteers working on the Eclipse open-source project are asked to follow the guidelines published to the Eclipse API Central. The Qt Application Development Framework has The Little Manual of API Design. And the examples could continue… But if it looks like we are going to use the “everybody else is doing it” fallacy here, there is much more to it. We believe you can derive very concrete and palpable benefits from taking a disciplined approach towards API design, and we will discuss these next.

Our claims

If books are judged by their cover, then software platforms, services, frameworks, and libraries are judged by their APIs. When developers evaluate the APIs, they are likely passing judgment on the entire service or component. Paying special attention to API design is a smart investment towards making a good impression, especially since publicly visible APIs are typically only a tiny fraction of the entire code base.

Developers and organizations are increasingly realizing that API quality directly impacts the quality of their own code. It might sound weird to hold API authors responsible for the quality of someone else’s code, but this happens more and more. Copy-and-paste style programming, boilerplate code, and similar questionable practices used to be blamed on the lack of programming skills until it was noticed that sometimes even the smartest, most careful developers can only avoid them by painfully wrapping APIs with their own classes and methods. It does not take a genius to figure out what these wrappers are: efforts to eliminate or isolate the effects of API deficiencies. Since APIs are written once, but used many times, calls to fix such APIs are getting louder. With thousands of public web services and free open source components to choose from, the days when developers silently put up with whatever APIs we gave them are long gone.

Any effort put into making APIs easier to learn, easier to use, and easier to troubleshoot directly translates into considerable programmer productivity gains. Let’s return to the main premise of the opening paragraph: today developers spend most of their time learning APIs, using APIs, and debugging code that is nothing more than moderately complex business logic wrapped around multiple complex API calls. It should come as no surprise that better APIs make developers more productive.

Concrete data supports the above claim. Studies conducted in 2007 by a team of researchers lead by Brad Myers and Jeff Stylos determined that even minor changes to APIs, for example replacing factory methods with regular constructors, can lead to significant programmer productivity gains. Moreover, the difference in productivity was measured regardless of the experience level of the developers. While the measured times varied little among participants using constructors, it showed differences up to a factor of 10 between participants using the factory method, a clear sign that they were struggling with the API. The same surprisingly large difference in productivity, up to a factor of 10 times, was measured when a method was moved to a different class within the API, or even after the same team redesigned the API of a big and complex SAP Business Rules Framework. Precise productivity measurements like these are difficult, and published results are still rare. While most claims about big increases in programmer productivity are only supported by anecdotal evidence, we have little reason to doubt their trustworthiness.

While numerous studies show that API quality is the main driving factor of software adoption, many developers and quite a few organizations are still not convinced. Perhaps they did not read Brian Foote’s The Selfish Class, which claims that “Software artifacts that cannot attract programmers are not reused, and fade into oblivion.” When adoption doesn’t happen, it is still common to blame the programmer’s monumental ego, FUD spread by the competition, or even the mystifying Not Invented Here Syndrome, rather than poor API quality.  But often you don’t even know where to start because many APIs lack useful documentation. In addition, some APIs are so closely tailored for a specific usage scenario that they are tedious or even impossible to use in a different context. Others come with so many dependencies that it is indeed easier to re-implement the functionality from scratch. As long as API quality remains low, software adoption rates remain low.

What are good APIs

But what are good APIs?  What appeals to programmers? What qualities should an API possess to attract them? Where should we direct our efforts?

We are repeatedly told to design intuitive APIs, although practical advice on how to do it remains in short supply. Wikipedia defines intuition as “understanding without apparent effort”, while Merriam Webster describes it as “the power or faculty of attaining to direct knowledge or cognition without evident rational thought and inference”. The truth is that the intuitive thought process is so quick and so closely associated with words like natural, straightforward, expected or obvious that nobody completely understands how intuition really works. Most contrast intuition with complex logical reasoning or learning by trial-and-error. Often, it is easier to identify counter-intuitive APIs from a sense of deep frustration we experience while using them. While it is hard to pinpoint precisely what makes APIs intuitive, some API design best practices, such as considering the perspective of the caller or striving for consistency are known to turn out intuitive APIs. We will discuss these practices in future installments.

We are also constantly reminded to keep APIs simple. We are even encouraged to leave functionality out to prevent them from getting complex. While the desire to satisfy 100% of all imaginable use cases unquestionably leads to overly-complex APIs, one cannot always simplify APIs by leaving out features. Obviously, there is a limit beyond which crippled APIs are no longer useful. We will discuss how to tell if APIs are getting complex and techniques for keeping it simple in a separate installment. Simplicity is an important requirement, but it needs to be carefully balanced against many other requirements.

Self-proclaimed experts are quick to point out that APIs should be easy to understand, remember, and use, but most of them fail to follow up with practical advice. Let’s start with the observation that understanding programs is quite different from understanding colloquial English. It is more like understanding a text from an exotic field of science.

“In this paper we study top quark production from string balls at LHC and compare with the parton fusion results at NNLO using pQCD. We find significant top quark production from string balls at LHC which is comparable to standard model pQCD results. We also find that ds/dpT of top quarks from string balls does not decrease significantly with increase in pT, whereas it decreases sharply in case of standard model pQCD scenario.”

A particle physicist familiar with the terms used can easily decipher the meaning of the above text. Good APIs strive to maintain programmer familiarity by minimizing the number of new terms and concepts and reusing existing programming vocabulary wherever possible. When naming new concepts they stay as close as possible to the spoken language and respect the established conventions of the programming environment. APIs that ignore this requirement are just as difficult to understand as the text above. Choosing memorable names is important enough to have an entire installment on this topic alone.

Speaking about ease of use… With so many APIs to work with, developers don’t have the time to thoroughly evaluate them. Instead, they just give them a quick test drive. If an API doesn’t allow for this kind of experimentation, or if the first impressions are not positive, it will not be given further consideration. We should encourage experimentation by making core scenarios very easy to implement and by providing code samples developers can play with. It helps with experimentation if APIs are safe to play with. Safety also plays a crucial role in the later phases of development, reducing the number of issues we need to debug and fix. API safety is often equated with good error detection and reporting but it is more than that: the API should make it impossible or hard for the developer to get it into a bad situation. Rico Marini calls this concept the “Pit of Success”. We will talk more about making APIs safe in a future installment.

Because developers like to experiment with APIs, they rarely read the documentation up-front. This raises the question of whether APIs need to be documented at all. To answer it, let’s remember that in everyday speech we can understand a message even if parts of it have been lost; for example we can read an article from a ragged, dirty newspaper picked up from the subway floor. The same is not true for short, terse messages like Tweets or SMS. A single missing word can make such messages incomprehensible. We say that such messages lack redundancy.  APIs also lack redundancy and the main purpose of documentation is to provide it. Redundancy should not be confused with repetition; it is the same information but conveyed in a different form, increasing the chances that it will be understood. Simply repeating the information found in method signatures in the documentation does nothing to help. We will discuss writing helpful documentation in a future installment. Because specifying behavior accurately is critically important and challenging at the same time, we will discuss this topic separately.

Purpose-made APIs

It is important to realize that the above-discussed API qualities do not appear spontaneously and that API design requires special attention. This is a crucial point. Many developers still doubt the need for a separate API design step because they see software interfaces spontaneously emerging at module boundaries during implementation. But experience shows that such spontaneous software interfaces have serious shortcomings when used as APIs, not because of developer negligence or incompetence, but because the design trade-offs we routinely make during implementation simply do not result in good APIs. To make matters worse, fixing such APIs after they are published is extremely difficult. As Joshua Bloch says, “Public APIs, like diamonds, are forever. You have one chance to get it right so give it your best”. This thought alone should be enough to question the practice of letting APIs emerge spontaneously. APIs won’t improve until API design is separated from the implementation effort and it is treated as a separate task. We will call such APIs purpose-made APIs.

Purpose-made APIs are much more likely to contain intuitive, meaningfully named methods and types, more likely to be carefully documented, and more likely to come with helpful samples, tutorials and unit tests. Such APIs are often simple, yet complete, and can evolve over time without breaking their clients; they have few dependencies and pack a lot of functionality. They are safer to use, encourage experimentation and support writing fast and efficient client code. The list isn’t complete. It just sketches the background against which Erich Gamma’s statement should be understood: “A key lesson here is that API is not just a documented class. And, APIs don’t just happen; they are a big investment.”

Creative Commons Licence
This work is licensed under a Creative Commons Attribution-ShareAlike 2.5 Canada License.