Update: A small elaboration of this has been published on the Architects zone at DZone.com. Read it at http://architects.dzone.com/news/intention-revealing-designs.
The value of naming of variables, methods and classes is highly underestimated. Given that code is read more than it is ever written, the readability of your code will get more attention than your original thought process that led you to the code. I will be so bold as to say that the reader really does not care how you arrived at those lines of code. The reader is actually focused on what you mean by the code and not how it does its magic.
Often the problem lies in carrying implementation details into the name of the intention. For example, assume you have designed a social network as a directed graph and you want to find all circles of friends that are in this social network (e.g. Joe knows Mary who knows Peter who knows Joe). Since it’s a graph, we can find all cycles in the graph, or closed loops. So, you write code such as:
class ObjectGraph { public Collection<CyclicGraph> findCyclicSubgraph() { ... } } class CyclicGraph extends ObjectGraph { ... }
This does not reveal any intention of finding circles of friends nor does it surface the domain adequately. The same code with intention revealing names just reads better:
class SocialNetwork { public Collection<SocialNetwork> findCirclesOfFriends() { ... } }
There’s a subtlety hidden in the refactored code. Both the circle of friends graph and the graph of the entire network are of the same type. SocialNetwork is a composite and has the advantage that all operations executed on a composite will yield a composite of the same type as well. In other words, all operations on SocialNetwork will yield another SocialNetwork. That is also intention revealing design as well as a bit of suppleness.
Here’s another example. You wish to set a boolean to indicate that an object is dirty in your cache.
public void setDirtyFlag(boolean dirtyFlag) { this.dirtyFlag = dirtyFlag; }
But refactored with intention revealing names, the purpose is now explicit.
public void dirty() { this.dirtyFlag = true; } public void clean() { this.dirtyFlag = false; }
Whenever you are writing code, ask yourself: “Is my intention clear and unambiguous?” If the answer is “No!” then your code is most probably lacking readability. There are refactoring techniques and many patterns that can be used to increase the suppleness of your design, but just spending a few extra brain cycles on the names that you use in your code can make an enormous difference in displaying your intention and not your implementation. This alone will increase readability and reduce maintenance of your code.
This is one of my favourite topics. I was first exposed to the importance of “semantics in design” in the early 1990’s. In addition to aiding in readability, naming classes and attributes accurately and consistently with business terminology helps to maintain a purer design and avoids inheritance hierarchies that support “technically” common functionality. If class names make their intent and purpose in life unambiguous then ancestors in the resulting inheritance hierarchy will embody behaviour and properties common to the corresponding business concepts they represent, and the design will be more robust. This robustness will become critical when it comes to implementing changes to keep the system in line with the changing business.
Stefan
There is a tension between implementation reuse and intention revelation. Case in point — if you need a graph cycle detection algorithm, there are many around but phrased in terms of nodes and graphs etc. But you’ll find very few called “SocialNetwork”.
Perhaps the situation is better solved using layered abstractions, which successively reveal intentions. E.g. build on the graph library (i.e. use it as the underlying implementation), but call your new layer SocialNetwork and hide the graph structure from clients.
Andrew
Hi Andrew,
Absolutely true. That is what I intended by the example but was, perhaps, not explicit. Hmmm, I should have shown DirectedGraph as a private member of the SocialNetwork class. That would have made the example a lot clearer.
But be guarded about how the layers of abstraction are structured. For example, extending SocialNetwork from DirectedGraph may not be good idea because all DirectedGraph operations may not be applicable to SocialNetwork.
If that is the case, then encapsulate DirectedGraph in SocialNetwork and expose the relevant operations using intention revealing names and in the implementation delegate the operation to the DirectedGraph.
Aslam