XNSIO
  About   Slides   Home  

 
Managed Chaos
Naresh Jain's Random Thoughts on Software Development and Adventure Sports
     
`
 
RSS Feed
Recent Thoughts
Tags
Recent Comments

Big Upfront Test Creation in Legacy Code is a Bad Idea

Wednesday, March 9th, 2011

When confronted with Legacy code, we usually run into the Test-Refactor dilemma. To refactor code we need tests, to write tests, we need to refactor the code.

Some people might advise you to invest time upfront to create a whole set of tests. Instead I recommend that every time you touch a piece of legacy code (either to fix a bug or to enhance the functionality), you perform a couple of safe refactoring to enable you to create a few scaffolding tests, then clean up the code (may be even test drive the new code) and then get rid of the scaffolding tests.

Even though this approach might appear to be slower, why does this approach work better?

  • You start seeing some immediate returns.
  • On any given system, there are parts of the system which are more fragile and needs more attention than others. There are parts of code which we actively touch and others we rarely touch. When we have a limit time, it does not make sense in investing effort to create tests for areas that are fairly stable or rarely changed. Big upfront test creation might not take this aspect into account. So you might not get the biggest bang for your buck.
  • The first few tests we usually write are fragile tests. But we won’t get this feedback nor the opportunity to improve the quality of our tests until its too late.
  • When we get into a test creation mode, everyone is focusing on creating more and more tests. Finally when we start using the tests we’ve created, a small change in production code might breaks a whole bunch of tests. First few times developers wonder what happened, but if this generates a lot of false-negative (which usually they do), then developers start ignoring or deleting those tests. So the investment does not really pay for itself.
  • Also when we have a whole lot of tests prematurely written, they start getting in the way of refactoring and genuinely improving the design of the code. (Defeats the whole point of creating test upfront so we can refactor.)
  • People get too attached to the tests they had written (it was a big investment). They somehow want to make the test work. People fail to realize that those fragile tests are slowing them down and getting in their way.
  • Unless the team gets into the habit of gradually building better test coverage, they will always play the catch up game with requirements constantly changing. (Remember we are chasing a moving target.)
  • Its usually hard to take a fixed (usually long) duration of time off from CRs and bug fixes. People will be forced to multi-task.

I encourage you to share your own experience.

    Rewrite v/s Refactor Dilemma

    Tuesday, December 22nd, 2009

    A lot of developer have a tendency to suggest rewriting legacy code instead of refactoring. Personally I don’t think one approach is better than the other. Following are the issues/issues one needs to consider with each:

    Rewrite:

    • Difficultly in coming up with Cost and Time estimate. (Its best to pull some numbers off the hat)
    • What if new bugs are introduced during rewrite?
    • Similarly how do you avoid unintentional behavioral changes?
    • What is the guarantee that you won’t end up with the same problems?
    • How do you get Management buy-in? Let’s say even if you get Management buy-in, how do you motivate developers to rewrite existing apps?
    • While rewriting, we’ll certainly introduce new technologies. How do you deal with problems introduced by new technology?
    • How to handle change requests during rewrite?
    • How to avoid not over engineering the solution?
    • How do you deal with various Political Issues around technology, tools, team, etc.?
    • How do you avoid Resume Driven Development?

    Refactor:

    • How do you deal with Refactor-Test Dilemma (Egg and Chicken problem) ?
    • How do you motivate the existing team to continue and refactor code?
    • How do you effectively identify the inflection point?
    • How to avoid big changes (in the name of refactoring) instead of baby steps?
    • It takes quite a lot of time and effort to understand legacy code
    • Even while refactoring developers might have a tendency to over engineer (esp. coz most developers conclude that the original developers did not think well and build an extendable solution)
    • To Refactor effectively it certainly need developers with higher skill-set. Can you find them?
    • Many times you’ll run into difficultly in breaking dependencies. How would you deal with it?

    In my experience a thin-sliced hybrid approach seems to work the best both from technical and business point of view. In a subsequent blog I’ll explain what I mean by thin-sliced hybrid approach.

    Confronting the Fear of Legacy Code

    Wednesday, September 16th, 2009

    When faced with Legacy Code, I’ve found 3 possible options to deal with them:

    • Leave it alone for now: Very rarely used, code seems to work fine.
    • Piecemeal Refactoring: When its difficult to understand what the code does and how it does what it does. Its time for safe, slow and cumbersome refactoring process.
    • Rewrite: When its clear what the code does, but it very difficult to understand how it does what it does, it time to rescue the code by rewriting it from scratch. This can be applied at various levels (whole code base, single module, class or method).

    To Rewrite or to Refactor?

    One can easily spend hours or days trying to refactor some code, when clearly (in retrospect) rewriting the code would be a better option. Sometimes you decide its better to rewrite the code and end up implementing something that does not work in all situations or we miss out something important. Unfortunately there is no clear guideline when I would choose to refactor code v/s rewrite the code. The key to me is, if I understand what the code does not necessarily how it does what it does, then its time to rewrite the code.

    Rewriting code: Play it safe

    The analogy I use is, rewriting code is like building bridges. You know that the bridge helps you get from point A to point B. It might be very complicated and risky to use the bridge any more. But that does not mean you’ll go and blow the bridge apart. Instead you would slowing start building a new bridge along side. When the new bridge is ready, you would divert a sample traffic on this bridge and see if it actually works. If it does, then you migrate all the traffic to the new bridge and blow the old bridge apart.

    I use the very same technique when rewriting code. During the process, I might leave the code working but in a much more messier (worse) state. During CodeChef TechTalks in Bangalore, Sai told me that he refers to this as an “Expand and Contract” cycle. You are temporarily expanding your code base so that you can come back and clean it up.

    When I’m rewriting code, I find black-box style automated tests very helpful. If you don’t have tests, it might be worth investing the time to write a few.

    Where to begin Refactoring Code

    • Outside-In: Start from a higher-level and refactor (delve) into the crux
    • Inside-Out: Start refactoring the crux and work your way out

    At times its difficult to identify the crux and I spend some time exploring (via refactoring) before I can choose an approach. Tests can be a great probe to understand the code.

    When refactoring legacy code, I usually use the Scaffolding Technique to break the Catch 22 situation (To refactor we need tests, to write tests we need to refactor). Scaffolding tests don’t necessarily have to be UI tests, I’ve used Unit tests as scaffolding tests as well.The key thing is they are temporary and meant to help you get started.

    Thanks to the folks @ the Legacy Code BoF @ CodeChef TechTalks in Bangalore who prompted me to write this blog.

    Redefining Legacy Code

    Wednesday, September 16th, 2009

    Michael Feathers did a great job by redefining legacy code to: “Code without Tests”.

    Over the years, I’ve dealt with code which had tests (unit, functional or both). Some of it was even test driven. But it was extremely difficult to understand and maintain the code. The code-base exhibited the same problems as Legacy code.

    What does this mean? IMHO, it means we need to broaden our definition of Legacy Code.

    Legacy code is code that developers fear facing. Legacy code does not communicate its intent and has a very convoluted design. It is code with high viscosity which encourages sloppy job by the developers and makes it extremely difficult for them to do the right thing. Abundance of Code Smells, lack of Tests, long feedback cycles, unpredictability, etc : all of these are contributing factors.

    Refactoring Legacy Projects: Scaffolding Technique

    Sunday, May 24th, 2009

    If you’ve inherited a Legacy Project (project without any tests) and say you want to enhance an existing feature, where do you start?

    In such situations, I find myself building some form of workflow tests (scaffolding). I start off using a record and play back testing tool to record couple of scenarios for the feature, I want to enhance. Most often, I would take the recorded tests and covert them into a re-entrant, independent scripts. So that I can execute them over and over again, without needing manual intervention. Basically this would mean, automating the set up and tear down of the application’s external dependencies like data-stores, email servers, etc correctly. This should not take more than a couple of hours to configure.

    This helps me build the initially safety net to start off. This also gives me a decent understanding of how the feature works. Now I can go inside the code, change something really small and see what impact it has on my tests. Some times I tweak my test to see what impact it has on the feature. Basically I’m using this test as a probe to gain deeper understanding of the feature’s functionality.

    Doing this give me some confidence to jump in and start refactoring the code, so that I can create an inflection point, break dependencies and start writing unit tests around the core of my feature. In couple of hours, I should be able to build a solid safety net, around my feature using unit tests and/or business logic acceptance tests.

    At this point, I almost always, go and delete the initial workflow test that I had built. This is the reason, I call this approach as the scaffolding technique.

    • Build some initial workflow tests to help you get in there,
    • Make the necessary code/config changes to write direct tests
    • Gradually build a solid safety net around the feature
    • The scaffolding (initial workflow tests) did its job, now its time to throw them away
    I demonstrate this technique when we do the Refactoring Fest. We take VQWiki (an open source Java wiki, with Zero tests) and build our scaffolding using Selenium.
        Licensed under
    Creative Commons License