oftware systems rarely remain static once they are put into production. Most businesses undergo changes to their processes and so the systems that support them must constantly be modified to meet the new requirements. These changes are typically implemented as patches to the current system, with as little architectural redesign as possible. While this allows the changes to be implemented faster, over time the patches stress the system to the point where the cost of maintaining the system and implementing additional changes becomes exorbitant.
When that time comes, a major effort is devoted to rolling out a new system. This initiative must include a data conversion and sometimes a period where both systems run in parallel. Rolling out new systems is very expensive and carries its own risks of failure.
But there’s another factor, less often discussed, that also stresses our existing systems over timethat’s the compromises that the original system’s programmers had to make in order to get it out the door in time. Every programmer I know of has a list of things he would have designed or coded differently for every project he’s participated in. The compromises often take the form of less elegant, flexible, or maintainable code. While these compromises win developers time up front with a quicker release, they cost us dearly over time, as it is becomes harder to add or modify features of the system.
Shifting the Plates
The pressures building up on our systems can be compared to the pressures that build up against the tectonic plates of the earth. If those pressures are released in a controlled and gradual manner, major disturbances are avoided, otherwise an earthquake occurs with all the associated damage. To solve the problem of code maintenance, we need a similar valve releasea process that will reduce the stresses on our systems intermittently, thereby extending their lifespans and reducing the costs of maintaining them.
The answer to this dilemma seems simple: a proactive system of gradual refactoring will keep systems running acceptably and should eliminate the need for a total system overhaul. First, we need to identify the areas that have grown difficult to modify or maintain. Then, instead of waiting for our system to become unusable, we will proactively modify these areas to improve their architecture and structure.
While the benefits of refactoring might be clear to programmers, it is not easy to demonstrate the business benefits of reengineering old code. Rarely will a business have the luxury or willpower of choosing refactoring over new development. (Microsoft recently said it would do exactly that; halting all new development in order to fix security-related structural problems in existing systems, but even still it has scheduled only about two months for the analysis of millions of lines of code.) Businesses are typically focused on the next feature set they require.
Therefore, in order to be successful, the refactoring process must work in tandem with new development initiatives. In fact, a synthesis of the two processes can produce a win for everyone.
- You are far more likely to succeed in convincing management to accept a slight delay in a new feature implementation than to halt new development for refactoring.
- In some cases refactoring can actually speed the implementation of a new feature by eliminating the structural awkwardness and reducing bugs.
- The overhead for the design, coding, and testing of the refactoring effort is reduced, as elements of all these activities must be done for the new feature regardless. Refactoring simply adds an incremental level of effort into these activities.
Combining the two processes also allows us to combat one of the largest morale issues faced by programmers supporting existing systems: The Boredom Effect.
The Boredom Effect
Programmers consider supporting existing systems a very poor cousin to rolling out a new system. The daily routine of supporting systems seems to consist of wading through hundreds or thousands of lines of other people’s code and making slight patches to effect fixes or new features. Without the ability to refactor, the support programmer sees his future as living with inelegant and rough code that he has no chance of fixing. No glory is attached to those finding themselves stuck with this unenviable task.
Rolling out a new system, with the attendant design activities and exposure to new technology, is considered the better place to be. Of course this is exactly contrary to what the business really needs. The business needs long lasting systems, not frequent system replacements!
Allowing programmers to fix what they find wrong as they work can be a great motivator, increasing job satisfaction and morale. Over time, as support programmers modify the current system, they will come to identify closely with it and take pride in their accomplishments.
But how much refactoring can you reasonably expect to do as part of a new feature implementation?
Choosing What to Refactor
In choosing to refactor as part of a regular implementation of new features, the largest part of the decision-makingwhat scope the refactoring effort should takehas already been made. The refactoring is limited to the parts of the system that relate to new features being designed. But within this definition, there is room to maneuver.
Generally, when choosing what to refactor you need to weigh the following issues.
- What is the amount of additional time added to the project due to the refactoring?
- How great is the risk that the refactoring will produce a bug in the existing system?
- How much “stress” will be released by the refactoring effort?
It’s valuable to look at some different possibilities for refactoring and try to measure them against the three questions outlined above. Because I spend most of my time coding SQL, I have provided examples in that language. Each organization will have different methods of software development and technologies so these categories and measures will need to be modified appropriately.
|Risk of Introducing bugs
|Poor syntax?Changes to the code to clarify it and improve readability.
|An example of this would be taking a bunch of If statements and recoding as a case statement.
|Badly written code can take much longer to understand than necessary and is frustrating to live with.
|Small?The key issue is whether the code (badly written or not) is understood. The chances of a bug being introduced can be minimized by having two people read the code that has been changed.
|This level of refactoring should always happen whenever the code in question is being modified anyway.
|Changing an algorithm?Sometimes we discover that there is a simpler and clearer solution to the problem.
|Some common occurrences; a solution that uses procedural logic (i.e. processing one record at a time) instead of using a set-oriented approach. Another example is taking logic branches (i.e. if step =5 next step = N) and driving it via a table.
|Depends?The new method will need testing, the extent of which depends on the level of complexity.
|This is a more extensive re-factoring and can produce much simpler and cleaner code.
|Medium?We need to prove that we have identified the correct way of solving the problem.
|Emphasis should be on a simpler algorithm, not a faster one, unless performance is an issue. Weigh this possibility along with others that suggest themselves on the project.
|Modularizing code?Often enough, the code is written as one chunk and could be broken down into smaller and more manageable sections.
|I’ve seen stored procedures that are well over five hundred lines. That’s a lot of code to take on in one piece.
|Moderate?we are not necessarily re-writing the code, simply breaking it up into separate procedures or functions.
|Small pieces of code are much easier to modify and work with. The level of effort required supporting drops dramatically with well modularized code.
|Moderate?We are reshaping the code, but we are not necessarily rewriting it.
|Walk through the proposed restructuring before attempting it. Balance this opportunity with others on the project.
|Modifying schema?As the business changes, we realize a way of modeling the data that better reflects the current business.
|This can be as simple as renaming some fields to better reflect their use to adding or dropping whole tables and or modifying the relationships between tables.
|Moderate?Can be higher if a significant data conversion is needed.
|A bad data model can lead to garbage data. It also significantly increases the cost of maintenance.
|Moderate to high?depending on the amount being changed and the data conversion required.
|Because of the risks associated with anything more than a trivial change to the schema, this needs to be well thought out and is typically only justified when the business has changed enough that the schema must change.
Putting it All Together
If you’ve successful evaluated the overhead for refactoring and made strategic decisions about where to tackle it and where not to, you will have a fairly simple process for driving your refactoring effort. Whenever you have a feature to implement, you simply identify the parts of the system that you need to modify and create a proposed design for the new feature. During this stage, with a little additional effort, you can:
- Identify the candidates for refactoring among the areas that you need to modify for the new feature.
- Assess the benefits, risks, and effort of each refactoring candidate. Your estimates will get better as you do more refactoring.
- Throw out of consideration anything that increases the time of the project beyond the agreed-upon timeframe. Beyond that, the actual decision will depend on the details of each candidate. The team should agree on the targets and the intended benefit.
Once the refactoring is accomplished, evaluate whether you’ve met your goals. Is the code easier to read? Is the algorithm less complex? Is the schema more flexible or easier to query? While you will probably never revert to the previous version, this process will help you make more informed decisions the next time.