Home arrow Agile Journal arrow Achieving Global Agile Development - December 2007
Revisiting Refactoring PDF Print E-mail
Written by Amr Elssamadisy   
Saturday, 08 December 2007
december07refracturingsmallRefactoring is one of the cornerstones of the technical agile development practices. It is the mechanism that allows the design and architecture of a system to evolve over time.  It is one third of the red-green-refactor loop and the core of test driven development (TDD). But does it really deliver on its promises? If you and your team are diligent writing tests and refactor mercilessly will your software evolve well and easily? Is the cost of refactoring always small and affordable?  Refactoring is not a silver bullet, and sometimes is painful and expensive, so we cannot rely on always refactoring with a limited cost. However, any design, no matter how appropriate today, will be inappropriate tomorrow as the requirements change, and refactoring is our best tool to evolve our designs and architectures.

 

Refactoring Defined
To "refactor" code means to change the structure of the code without changing its behavior.  In all but the most simple of programs, this practically means that there are tests that act as a ‘safety-net' to let you know when your design changes have changed behavior.  Refactoring cannot practically occur without tests.  Therefore all teams that want to change/evolve their design must have automated tests to enable the refactoring.

So why refactor? In essence, we refactor to make things better.  When the design no longer meets the problem at hand - whether it is because the requirements have changed or we have a better understanding of the problem - it helps to change the design to better match the problem.  This makes the code more readable for others and therefore easier to understand, and readability is important since we spend much more time reading code than we do writing it.

Theory: Refactoring Leads to Good Design

Refactoring, as mainly used in the Agile community, is almost always found within the red-green-refactor loop of Test Driven Development (TDD).  Write a failing test (red), make it pass (green), then clean up the code (refactor).  It has been claimed that this simple process will lead to designs and architectures that evolve into just what you need. It has been claimed that if you build for only what is required for the requirement at hand that you will be able to add the missing functionality later by refactoring at a small cost. But is this necessarily true?

Advertisement
No it is not - not always.  In fact, evolutionary designs and architectures can be just as bad if not worse than upfront designs and architectures.  Teams - especially large ones - have a tendency to reinvent the wheel in different parts of the systems as different solutions to similar problems evolve.

Moreover, refactoring is a greedy algorithm,[i] which means that we make locally optimum choices - via YAGNI[ii] - with the hope of finding the global optimum.  Greedy algorithms are notorious for getting stuck in local minima.  Likewise, refactoring keeps your code and design clean - but not optimal.  Every once in a while large refactorings need to be made, and they are not easy.

Large Refactorings are Hard
Large refactorings are significantly more difficult than the default refactorings that have been catalogued.  They should not be underestimated.  For example, retrofitting fine-grained security control in a system that was not designed with security in mind will be a time consuming and difficult task.  But they can be done if a safety-net of tests is written for the application.

If, on the other hand, there were no tests then this type of retrofitting would be significantly more expensive (if not impossible).  This is why many traditional applications - written without a safety-net of tests - are full of duplicate code; developers cut-and-paste code instead of modifying the design because of the prohibitive costs and dangers of doing so. 

So it seems we're damned if we do and damned if we don't. What is the solution?  Since designs do become stale and we do tend to over-generalize, we should always have a safety-net of tests to allow us to respond to the changes over time.  At the same time, we need to get away from pure YAGNI.  There are times where we know with a large degree of certainty the requirements of the application upfront - the larger the degree of certainty we have in the overall requirements, the greater amount of initial design needed .

Different Viewpoints
Here are some of the current viewpoints regarding the effectiveness of Refactoring and TDD:

Jim Coplien, in "Religion's Newfound Restraints on Progress," takes aim at testing and test driven development.[iii]  Since Refactoring is an integral part of TDD, the comments on TDD implicitly apply to refactoring  Coplien states:

Integration and system testing have long been demonstrated to be the least efficient way of finding bugs. Recent studies (Siniaalto and Abrahamsson [http://mango2.vtt.fi/virtual/agile/publications.html]) of TDD show that it may have no benefits over traditional test-last development and that in some cases has deteriorated the code and that it has other alarming (their word) effects. The one that worries me the most is that it deteriorates the architecture. And acceptance testing is orders of magnitude less efficient than good old-fashioned code inspections, extremely expensive, and comes too late to keep the design clean.

... TDD, about engaging the code. TDD is a methodology. TDD is about tools and processes over people and interactions. Woops. ...

James Shore, in "The Agile Engineering Shortfall," acknowledges that there is a shortfall of TDD when taken by itself as a practice.[iv]  However:

There is an agile engineering shortfall. Short planning horizons necessitate alternative engineering practices. The problem isn't with the practices, though--good agile engineering practices exist and work well. The problem is with agile methods that don't provide agile engineering practices, and with teams that adopt a small subset of the agile engineering practices (typically, just TDD). It's unfortunate, but no surprise, that they run into trouble as a result.

Kent Beck, in his new book Implementation Patterns, writes:

Three values that are consistent with excellence in programming are communication, simplicity, and flexibility.  While these three sometimes conflict, more often they complement each other.  The best programs offer many options for future extension, contain no extraneous elements, and are easy to read and understand.

... Flexibility can come at the cost of increased complexity.

... Choose patterns that encourage flexibility and bring immediate benefits.  For patterns with immediate costs and only deferred benefits, often patience is the best strategy.  Put them back in the bag until they are needed.  Then you can apply them in precisely the way they are needed.

Of course, to put the new functionality in later, when it is needed, you will need to change the design - i.e. refactor.

Conclusion
Refactoring is a greedy algorithm - it reaches local minima.  Large refactorings are very expensive.  They are many times prohibitively expensive.  Therefore pure reliance on refactoring without some upfront design and architecture is costly and suboptimal.

At the same time, no matter how good an upfront design and architecture is, it will grow out of date and be a bad one.  The development community is not a fortune-telling community; traditional upfront design and architecture over-generalizes in the wrong places.  Generalization and flexibility by design is costly and does not come for free.  A pure upfront design technique always fails over time.

As of yet, there is no silver bullet.  We know that designs and architectures will change as they fail to meet the requirements of a changing world - so we will have to refactor.  At the same time, we need to realize that not all refactoring is easy or cheap.  Where does that leave us?  It leaves us with doing the best we can; individually finding the balance of YAGNI and upfront design.  It depends on each one of us individually.

Good judgment comes from experience - and experience comes from bad judgment.  Refactoring is only as good as the developer performing the refactoring.  For that matter, design and architecture are only as good as the developer creating them.  The difference is, that with refactoring we have an option of learning from our mistakes and changing the design.  It is not perfect - and we should be aware of its shortfalls - but it is better than the alternative.


About the Author
Amr Elssamadisy is a software development practitioner at Gemba Systems, helping both small and large development teams learn new technologies, adopt and adapt appropriate Agile development practices, and focus their efforts to maximize the value they bring to their organizations. Gemba focuses on issues such as personal agility, team-building, communication, feedback, and all of the other soft skills that distinguish excellent teams.  Amr's technical background and experience (going back to 1994) in C/C++, Java/J2EE, and .NET, allows him to appreciate the problems of and support development teams 'in the trenches.'  Amr is also the author of Patterns of Agile Practice Adoption: The Technical Cluster, an editor for the AgileQ at InfoQ, a contributor to the Agile Journal and a frequent presenter at software development conferences.


[i] http://en.wikipedia.org/wiki/Greedy_algorithm

[ii] You Ain't Gonna Need It (YAGNI) [http://en.wikipedia.org/wiki/You_Ain't_Gonna_Need_It] tells you, the developer, not to design for any requirement that is not at hand.  It tells you that if you write tests diligently (usually via TDD) you will be able to refactor the code later and add the necessary complexity.

[iii] [http://www.artima.com/weblogs/viewpost.jsp?thread=216434]

[iv] http://jimshore.textdriven.com/Blog/The-Agile-Engineering-Shortfall.html

Comments (6)add feed
Amr Elssamadisy: ...
As for IDE refactorings..... You are absolutely correct.... To a point.

Consider this: You have done a refactoring where you "Extract Method" and the variables you choose for that method are not all satisfied/available for all of the instances where this code resides.... So you choose a default.

Now, assume further, that your code used by a client. That value, that you assumed a dummy value for, is now incorrect and the client will break. If you had tests for this, it would have been caught.

Refactoring tools are only the safety net when the user has absolutely no input into the refactoring.
1

January 18, 2008
Amr Elssamadisy: ...
Actually, there is large refactoring. Check out the last section in Fowler's book. Do a Google search for large refactorings and you'll find articles from many in the Agile field discussing how to avoid it and how to deal with it.
2

January 18, 2008
Alan: ...
While the article seeks to alley the "silver bullet" aspect sometimes associated with contemporary techniques, it make a fundamental error in its understanding of refactoring. While I agree, there is no silver bullet, refactoring, when done properly, pays substantial dividends to the customer. The error in the article is in reference to "large refactorings". There is no such thing! Refactoring is only ever small. Re-engineering, on the other hand, is difficult and often expensive. If refactoring had been suitably applied in the first instance, re-engineering would not be necessary. That is the central tenet of refactoring.
3

December 20, 2007
Fireblaze: ...
"objectively guarantee that no client will experience a regression" Like I said "Most IDE today got build in refactoring as a safety-net so the IDE is responsible for that the same result is build." that is you IDE handle that case, so you still have a safetly-net without the test.

So my question remains - What empirical bases does that article have for the statement that "Refactoring cannot practically occur without tests"?
4

December 20, 2007
John P: ...
Fireblaze:
What the article contends is that refactoring without having a safety net of tests is not a sound practice. If you take refactoring in its purest form, it means not changing the external view (interfaces, etc.) to the code or system in question. Under this definition, how can you objectively guarantee that no client will experience a regression without a comprehensive test suite?

If you loosen the definition of refactoring to that of redesign (IMHO, a dangerous definition), then sure, you *can* redesign a (sub)system without having a battery of tests, but your confidence in the result will be in question relative to a team, all else being equal, that has such a suite.

John
5

December 19, 2007
Fireblaze: ...
"Refactoring cannot practically occur without tests." What empirical bases do you have for this statement?

Most projects I have seen and read postmortems on have done refactoring without tests. Works fine. Most IDE today got build in refactoring as a safety-net so the IDE is responsible for that the same result is build. Refactorings like rename and introduce method are still practiced every day with out safety-net.
6

December 19, 2007
Write comment


Write the displayed characters


busy
Tags:
Click to add your tags...,
 
< Prev   Next >

Video News

Agile Poll

Select all that Apply
How are you building an Agile organization?
 
 
 
 
 
 
 
Agile Edge Conference
 

Coming Up - Editorial Calendar

  • August 13 - Quality Agile Development
  • September 10 - Agile News
  • October 08 - Valuable Agile Practices
  • November 12 - Introducing Agile to the Organization
  • December 10 - The State of the Agile Community
See the full 2008 Editorial Calendar >
Copyright © 2006 CMC Media, Inc. All rights reserved. All marks are trademarks of CMC Media Reproduction in whole or in part in any form or medium without the express written permission of CMC Media, Inc. is prohibited  
 
 CM Yellow Pages | ALM Expo | CM Today | Configuration Management Journal | CM Crossroads