By Ganesh Samarthyam, Tushar Sharma, and Girish Suryanarayana
“Refactoring is a needless rework – I want my team to get design right the first time!” told a project manager when one of us were discussing software design with him. Is he right?
Why should developers “waste” time improving the design of existing code when they can be more productive and add value to customers by adding new features or make fixes in the software? Why can’t designers or architects get design right the first time itself?
Many of us have the experience of working in a long-running software projects that span millions of lines of code. Such projects usually start with good design, but over a period of time the design degrades (what we know with the term “design decay”). Gradually, work slows down and it takes increasingly more time to make fixes or enhancements. To get the work going, as developers we introduce some more short-cuts. The code starts stinking and often it reaches a state where development comes to a stand-still.
What options do we have in this situation? Fundamentally there are only two options: rewrite or refactor. We can choose to throw away the existing software and buy a new one or rewrite it. This is what most developers prefer but is not very practical from the business perspective. Another option is to refactor and that is the really only practical option we have. Let us understand this with the help of an analogy.
Think of a burgeoning city like Bangalore – it is a perfect metaphor for the kind of struggles that software development teams undergo. As Grady Booch wrote in the foreword for our book:
“Cities grow, cities evolve, cities have parts that simply die while other parts flourish; each city has to be renewed in order to meet the needs of its populace… Software-intensive systems are like that. They grow, they evolve, sometimes they wither away, and sometimes they flourish…”
All three of us live in Bangalore. We love and hate the city at the same time. The weather is good, people are friendly, and it’s a heaven for like-minded geeks like us to live – so we love it. We hate it because it is over-crowded and has notorious traffic snarls. It is rightly infamous for its traffic woes.
It is hard to believe now, but Bangalore was a “pensioner’s paradise” just 30 years back. The existing infrastructure was adequate to live a peaceful life. With overcrowding (partly due to the IT boom), the city’s infrastructure has become inadequate. The traffic situation is so bad that many a times, it takes a couple of hours just to cross 15 kilometers in many of the arterial roads.
Is a complete overhaul of the city practical or feasible? No, not at all – we cannot afford to move people to other places and rebuild the city. Do you remember lessons from history? For example, Muhammad bin Tughlaq’s attempt to move the capital of his Sultanate from Delhi to Daulatabad in faraway Maharashtra resulted in a complete failure.
How about refactoring? That is perhaps the only practical solution we have. Refactoring (or restructuring) at different levels is needed. Major restructuring such as introducing metro lines are necessary for getting a long-term solution for the traffic problems. But in the short-term, such restructuring causes inconvenience and pain to the citizens affected by such restructuring. At a lower-level, constructing flyovers, underpasses, expanding roads, etc are the more practical and immediate solutions to the traffic problems of the city. Further, at an even more lower-levels, patching up the roads can enable a smooth ride.
Similar to restructuring the city at different levels, a software system can also be refactored at architecture, design, and code levels. Refactoring at each level eliminates the smells at that abstraction level. In our work on refactoring, the main focus is on refactoring for design-level smells. What are design smells? To understand them let us go back to the Bangalore city analogy and give specific examples.
- Missing roads: In many places roads are missing and one has to struggle with muddy roadsevery day.
- Uncontrolled access: Traffic is uncontrolled in many of the underpasses (especially where the traffic signals are missing) – one can go from any direction to any other direction (causing frustration and gridlock in peak hours)
- Bottleneck junction: There are junctions where it is possible to take numerous turns in different directions that lead to increased waiting time and single point of failure (e.g., the infamous total mall junction on outer ring road)
- Footpath road: A footpath is meant for walking and not for biking. It is not uncommon to see people using footpaths as road driving bikes to bypass traffic.
These are all symptoms or indicators of creaking infrastructure (i.e., design) and hence need to be restructured or regulated. Interestingly, we have similar cases in software as well where we can map above-mentioned traffic problems to software design smells:
- Missing abstraction: What should have been a class or an interface in design is instead encoded as strings, group of variables, etc. An example is the case of getStackTrace() method in Throwable class in JDK. The refactoring was introduced in Java 1.4 in the form of providing a programmatic access to the stack trace (i.e., the method StackTraceElement getStackTrace() was introduced in Java 1.4)
- Deficient encapsulation: The accessibility of the members is more than required leading to unrestrained access. An example is the Point class in JDK which has public data members x and y.
- Hub-like modularization: When a type has a large number of incoming as well as outgoing dependencies. An example is the java.util.Component class that has 498 incoming dependencies and 71 outgoing dependencies.
- Broken hierarchy: When supertype and subtype do not share IS-A relationship. For example, the Stack and Vector classes of java.util package do not share IS-A relationship.
Here, an important aspect to realize is that every engineering discipline has to deal with non-optimal or sub-optimal solutions and software engineering as a discipline is not an exception.
In fact, with changing requirements and rapidly emerging technologies, our discipline has more need for quick adaptation. This is one of the reasons why agile methods have wide-spread acceptance in the last decade and that is one of the reasons why agile gives considerable importance to refactoring.
Let us revisit the question we started with. “Is refactoring a needless rework?” – Absolutely not. It is of course necessary and forms the core practice for creating high-quality software. “Status quo” is not acceptable – a creaking city like Bangalore needs structural restructuring and improvements to sustain growth. “Is it acceptable for managers to expect their teams to get design right the first time?” Again, absolutely no – with changing requirements design becomes outdated and often transforms into a something that stinks. Continuous evolution is the only way to deal with changing requirements. The summary of this article: whenever you are confronted with this question of should you refactor, consider the city as an analogy to understand and convince why continuous evolution is the only practical way to live in it. You may delay it but you cannot avoid it.