Much has been said about the importance and complexity of designing good APIs (application programming interfaces). It’s hard to get everything right the first time, and even harder to change anything along the way; it’s like raising children. Experienced programmers have already understood that a good API offers the same level of abstraction for all methods, possesses uniformity and symmetry, and forms a vocabulary for an expressive language. Unfortunately, knowing the principles is one thing, but following them in practice is another. You know that eating sweets is harmful.
But let’s move from words to action and analyze a specific “strategy” for API design that I encounter constantly: designing the API to be user-friendly. As a rule, it all starts with one of the following “insights”:
- Why do client classes have to make two calls to perform one action? - Why should client classes make two calls to perform one action? - Why create another method if it does almost the same thing as the existing one? Why create another method if it does almost the same thing as the existing one? I’ll add a simple switch. - Listen, it’s elementary: if the second parameter string ends with “.txt”, the method automatically understands that the first parameter is the file name, so there’s no need to create two methods.
The intentions are good, but the proposed solutions are fraught with the risk of reducing the readability of the code that uses your API. The following call to the method parser.processNodes(text, false); carries semantic weight only for someone who knows how the method is implemented or has read the documentation. This method was created more for the author’s convenience than the user’s: “I don’t want to force the programmer to make two calls” in practice meant “I didn’t want to write two methods.” In principle, there is nothing wrong if convenience is used as a remedy against monotony, bulkiness, and clumsiness. But if you think about it, the antidote to these symptoms is efficiency, elegance, consistency – and not necessarily convenience. APIs imply the concealment of the internal complexity of the system, so it is quite reasonable to expect that designing a good API requires some effort. It is quite possible that it is more convenient to write one large method rather than a carefully thought-out set of operations, but which option is easier to use?
In such situations, more successful architectural solutions can be based on the metaphor of an API as a natural language. The API should offer an expressive language that provides the higher-level vocabulary sufficient to ask useful questions and receive answers. This does not mean that each possible question will be matched with only one method or verb. A vast vocabulary allows for conveying nuances of meaning. For example, we prefer to say run instead of walk(true), even though these actions can be considered the same operation performed at different speeds. A consistent and well-thought-out API vocabulary contributes to the expressiveness and clarity of higher-level code. And what is even more important – a dictionary that allows word combinations gives other programmers the opportunity to use the API in ways you did not foresee, and this is truly a great convenience for its users! When you feel tempted to combine several operations into one API method again, remember that the word “CleanYourRoomDon’tMakeNoiseAndDoYourHomework” is not in the dictionaries, even though it would be quite handy for such a popular operation.