462 words | 2 minutes to read
Very rarely these days does software get developed by a single person, and even if it did, the human condition is so fickle that what they deem is great one day may be considered dead wrong the next (trust me, I’ve seen far too many conversations where somebody asks “why are you doing it this way” in consternation and disgust, only to be told it was them). Fortunately, as we design API, we have a clear record of the decisions we have made in the form of our public API, and it can be quite easy to spot when something is deviating from this forming convention.
The short term benefit to having consistent API is that we reduce the risk of frustrating our users, and the long term benefit is that a consistent API ensures that when an end user arrives at a new section of your API, that they are more readily able to intuit how it should be used.
Some of the more important considerations around consistency are covered in the remainder of this best practice.
Ideally all API that must return a collection should be consistent, using only a few classes rather than all possible ones. A good subset of collections to return might be, for example,
Iterator (and in this case, never
Stream - but note that these are all valid return types - just in this example they were chosen to not be in the subset). Relatedly, if the API takes pain to not return null in most cases (for a certain return type), it is best advised to never return null for that return type.
Developers rely on their IDE to auto-complete their input, so consider the importance of API naming, to ensure related concepts appear adjacent to each other in the users auto-complete popup lists. An API should have a well established set of terms and reuse those terms all the time in: type names, method names, argument names, constants, etc. Method names like
Type.from(...), etc. should be used consistently, and never mixed. Our goal is to have a team-wide vocabulary that we use throughout our API.
APIs that overload methods to accept different numbers or types of arguments should always ensure that the ordering is consistent and logical. In some cases the arguments we pass into a method form some logical grouping, and in these cases it may make sense to introduce an intermediate argument type that can wrap these arguments. This reduces the risk that subsequent releases of our API will need to overload the method to accept more arguments. This also helps to aid our goal of API evolvability.