APIs, Boilerplate and Reinvented Wheels
What happens when there are too many APIs? The first thing that happens is you can't remember them all. Then people invent tools to ease the pain: popups with documentation, autosuggestions, navigating to the source code, etc.
Tools are great but enhancing our dodgy memory with popups and drop-downs doesn't address the root of this particular problem. The problem is too much stuff to remember.
Some of this is stuff we need. If a library gives us some new functionality then it might be worth the space it takes in our grey matter but most libraries offer at most a nugget of something new propped up by a supporting network of reinvented wheels.
String Reinvents the ListOne such reinvented wheel we are all familiar with is the String. It's an array-backed list of characters. Java's String and List have a lot of operations in common but the devil is in the details. Take a look at this handful:
These method pairs essentially do the same thing but they all have different names which means there is more to remember. They all have subtly different semantics too. For example
addAll() changes one list by adding the contents of another.
concat() creates a new String by copying characters into a new array.
This is partly due to one structure being mutable and the other immutable. However, Java currently has both mutable and immutable versions of Strings, and both mutable and immutable versions of lists. The List interface has optional operations which give you a mutable list when implemented or an immutable list when left unimplemented. You can easily imagine something like the following to tidy it up:
- ImmutableList: to replace String and immutable lists
- MutableList: to replace StringBuilder, StringBuffer and mutable lists
- Consistent method names and a common interface for methods that exist in both lists.
Other languages represent Strings as abstract data types. Two examples are Erlang which uses Lists to represent strings and C which uses char arrays to represent strings. With both, there is less to learn since the chosen data type is used commonly in the language. A further advantage is being able to leverage any library that processes lists/arrays to also process strings.
What Else Reinvents List?
Classes like String are essentially bespoke re-implementations of lists with their own interface but many classes hide one or more lists. Here are a few tell-tale signs that a class might be hiding a list.
- Getters that accepts an index.
- Methods with names like add or append.
In these cases, object orientated design tells us to make the list a private field and write methods that expose the list in a controlled way. Sometimes this will be necessary but often it will be safe to expose the list instead. This has the advantage of a consistent interface that everyone is used to dealing with. Here are a few circumstances that suit different kinds of lists:
When the list should be read-only
It should almost always be safe to expose an immutable list. This can be created without the performance penalty of copying by calling
When the list is editable anyway
If the methods you would provide in your API would let the list be edited by a client class then there is no advantage to hiding the list. You might as well give access to a mutable list. A great example of this is AbstractButton in the Java API. It has addActionLIstener(), removeActionListener() and getActionListeners(). The combination of these three methods will let client classes read and edit the listeners, albeit not in a convenient way. Exposing the mutable list would make it easier to process listeners and make the interface considerably smaller (especially considering AbstractButton supports fifteen other kinds of listener).
When editing must be controlled
Maybe the way a list is edited is important to an application. For example, maybe it's important that new items can only be added to the end or that items cannot be removed. In this case then maybe the best choice is to provide the editing methods via your classes API. I still maintain that it is safe to expose an immutable list. However, I would be wary of exposing a list with some of the optional editing methods disabled. A list where only some of the functionality works might be confusing to other developers.
When access must be controlled
For example, maybe access to the list must be logged. There are two options here. The easy option would be to include access methods that also do the logging in your own class. The alternative would be to write a decorator for the list. The latter would provide a more consistent interface to the client but would probably be a little more up-front work.
The Bigger Picture
I've used lists as an illustrative example but the same principal applies to other data structures. Sets, stacks, queues, maps and probably much more fall into this category.
It also applies to your own code:
If the structure of several of your classes is the same then consider if they are really specific examples of a general thing.
If the structure of several of your functions is similar then consider if they are really specific examples of a general thing.
08 September 2015