Agile FAQs
  About   Slides   Home  

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

Recent Thoughts
Tags
Recent Comments

Archive for September, 2009

Different Techniques to Stub/Mock Dependencies

Tuesday, September 29th, 2009

I’ve primarily used the following techniques to stub/mock out dependent classes while unit testing:

  • Using a Dynamic Mocking Framework like Mockito, EasyMock, JMock, RhinoMock, etc
  • Create a special subclass of the dependent class (or Interface) in the test package and use that to stub out dependency
    • One can choose to create an anonymous inner class if a full new class in a separate file cannot be justified as an act of sanity.
    • (Sometimes you might even subclass the class under test to inject behavior and dependency).
  • Have the test implement or extend the dependent class

Let’s see each technique in action with an example:

Problem: We are building a Coffee Vending Machine controlling software and trying to test drive the Controller piece.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Controller {
    private Panel panel;
 
    public Controller(final Panel panel) {
        this.panel = panel;
        //some start up logic here.
        panel.display("Please select a coffee type");
    }
 
    public void selectedCoffee(final CoffeeType type) {
        String price = "0.35$"; // some logic to compute price
        panel.display("Please insert " + price);
    }
}

Controller does whatever magic it wants to do and then displays some message on the panel.

1
2
3
public interface Panel {
    void display(String msg);
}

1. One way to test the controller is by using a Dynamic Mocking framework like Mockito:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestUsingMockingFramework {
    private Panel panel = mock(Panel.class);
 
    @Test
    public void displayCoffeeSelectionMessageOnPowerUp() {
        new Controller(panel);
        verify(panel).display("Please select a coffee type");
    }
 
    @Test
    public void displayPriceOnSelectingCoffee() {
        Controller controller = new Controller(panel);
        controller.selectedCoffee(CoffeeType.BLACK);
        verify(panel).display("Please insert 0.35$");
    }
}

2. Another technique is to create a TestPanel class in the testing folder:

1
2
3
4
5
6
7
8
class TestPanel implements Panel {
    public String msg;
 
    @Override
    public void display(final String msg) {
        this.msg = msg;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestUsingHandCodedStub {
    private TestPanel panel = new TestPanel();
 
    @Test
    public void displayCoffeeSelectionMessageOnPowerUp() {
        new Controller(panel);
        assertEquals("Please select a coffee type", panel.msg);
    }
 
    @Test
    public void displayPriceOnSelectingCoffee() {
        Controller controller = new Controller(panel);
        controller.selectedCoffee(CoffeeType.BLACK);
        assertEquals("Please insert 0.35$", panel.msg);
    }
}

3. If you don’t want the overhead of creating an extra TestPanel class, you can create an anonymous inner class instead.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TestUsingAnonymousInnerClass {
    private String msg;
    private Panel panel = new Panel() {
        @Override
        public void display(final String message) {
            msg = message;
        }
    };
 
    @Test
    public void displayCoffeeSelectionMessageOnPowerUp() {
        new Controller(panel);
        assertEquals("Please select a coffee type", msg);
    }
 
    @Test
    public void displayPriceOnSelectingCoffee() {
        Controller controller = new Controller(panel);
        controller.selectedCoffee(CoffeeType.BLACK);
        assertEquals("Please insert 0.35$", msg);
    }
}

4. One other technique I find useful sometimes is to have my test implement or extend the dependency (class or interface). So the test acts as the real dependency.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TestUsingTestClassAsPanel implements Panel {
    private String msg;
 
    @Override
    public void display(final String message) {
        msg = message;
    }
 
    @Test
    public void displayCoffeeSelectionMessageOnPowerUp() {
        new Controller(this);
        assertEquals("Please select a coffee type", msg);
    }
 
    @Test
    public void displayPriceOnSelectingCoffee() {
        Controller controller = new Controller(this);
        controller.selectedCoffee(CoffeeType.BLACK);
        assertEquals("Please insert 0.35$", msg);
    }
}

I’ve seen very few people use the last technique. Personally I think it has a place and time.

When would I use this technique?

  • Sometimes this technique can be very simple (not worth introducing Dynamic Mocking Framework yet nor worth the over-head of extra test helper classes)
  • I find this technique particularly useful when I don’t want to expose some state on the dependent class.
  • This technique takes you more towards interaction based testing rather than state based testing.

Avatars of TDD @ CodeChef TechTalks, Bangalore

Monday, September 28th, 2009

Recently I presented on Avatars of TDD at CodeChef TechTalks in Bangalore.

Artifacts from the tutorial:

(Yes, that’s me talking. Even though you can’t see me you have to trust me.)

For the demo, I used Alistair Cockburn’s problem from OOPSLA DesignFest. Feel free to download the source code from the demo.

Refactoring Teaser IV Solution

Sunday, September 27th, 2009

Its been a while since the Fourth Refactoring Teaser was posted. So far, I think this is one of the trickiest refactorings I’ve tried. Refactored half of the solution and rewrote the rest of it.

Particularly thrilled about shrinkage in the code base. Getting rid of all those convoluted Strategies and Child Strategies with 2 main classes was real fun (and difficult as well).  Even though the solution is not up to the mark, its come a long long way from where it was.

Ended up renaming IdentityGenerator to EmailSuggester. Renamed the PartialAcceptanceTest to EmailSuggesterTest. Also really like how that test looks now:

28
29
30
private final User naresh_from_mumbai = new User("naresh", "jains", "mumbai", "india", "indian");
private final Context lets = new Context(userService, dns);
private final EmailSuggester suggester = new EmailSuggester(userService, dns, randomNumberGenerator);
32
33
34
35
36
@Test
public void suggestIdsUsingNameLocationAndNationality() {
    List<String> suggestions = suggester.optionsFor(naresh_from_mumbai);
    lets.assertThat(suggestions).are("naresh@jains.com", "naresh@india.com", "naresh@indian.com", "naresh@mumbai.com");
}
38
39
40
41
42
43
@Test
public void avoidRestrictedWordsInIds() {
    lets.assume("naresh").isARestrictedUserName();
    List<String> suggestions = suggester.optionsFor(naresh_from_mumbai);
    lets.assertThat(suggestions).are("nares@jains.com", "nares@india.com", "nares@indian.com", "nares@mumbai.com");
}
45
46
47
48
49
50
@Test
public void avoidCelebrityNamesInGeneratedIds() {
    lets.assume("naresh", "jains").isACelebrityName();
    List<String> suggestions = suggester.optionsFor(naresh_from_mumbai);
    lets.assertThat(suggestions).are("nares@jain.com", "naresh@india.com", "naresh@indian.com", "naresh@mumbai.com");
}
52
53
54
55
56
57
@Test
public void appendCurrentYearWithFirstNameIfIdIsNotAvailable() {
    lets.assume().identity("naresh@jains.com").isNotAvailable();
    List<String> suggestions = suggester.optionsFor(naresh_from_mumbai);
    lets.assertThat(suggestions).are("naresh2009@jains.com", "naresh@india.com", "naresh@indian.com", "naresh@mumbai.com");
}

EmailSuggester’s optionsFor() method turned out to be fairly straightforward.

26
27
28
29
30
31
32
33
34
public List<String> optionsFor(final User user) {
    List<String> ids = new ArrayList<String>();
    List<String> variations = asList(user.lastName, user.countryName, user.countryMoniker, user.city);
    for (String variation : variations) {
        UserData data = new UserData(user.firstName, variation, user.lastName);
        data.addGeneratedIdTo(ids);
    }
    return ids;
}

This method uses UserData class’ addGeneratedIdTo() method to add an email id to the list of ids passed in.

47
48
49
50
51
52
53
54
55
private void addGeneratedIdTo(final List<String> ids) {
    for (EmailData potential : buildAllPotentialEmailCombinations()) {
        String email = Email.create(potential.userName, potential.domain, dns);
        if (userService.isEmailAvailable(email)) {
            ids.add(email);
            break;
        }
    }
}

This method fetches all potential email address combination based on user data as follows:

57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
private List<EmailData> getAllPotentialEmailCombinations() {
    return new ArrayList<EmailData>() {
        {
            add(new EmailData(firstName, seed));
 
            if (seed != lastName) {
                add(new EmailData((firstName + lastName), seed));
                add(new EmailData((firstName + lastName.charAt(0)), seed));
            }
 
            add(new EmailData((firstName + currentYear()), seed));
 
            if (seed != lastName)
                add(new EmailData((firstName + lastName.charAt(0) + currentYear()), seed));
 
            for (int i = 0; i < MAX_RETRIES_FOR_RANDOM_NUMBER; ++i)
                add(new EmailData((firstName + randomNumber.next()), seed));
        }
    };
}

I’m not happy with this method. This is the roughest part of this code. All the

if (seed != lastName) {

seems dodgy. But at least all of it is in one place instead of being scattered around 10 different classes with tons of duplicate code.

For each potential email data, we try to create an email address, if its available, we add it, else we move to the next potential email data, till we exhaust the list.

Given two tokens (user name and domain name), the Email class tries to creates an email address without Restricted Words and Celebrity Names in it.

30
31
32
33
34
35
private String buildIdWithoutRestrictedWordsAndCelebrityNames() {
    Email current = this;
    if (isCelebrityName())
        current = trimLastCharacter();
    return buildIdWithoutRestrictedWordsAndCelebrityNames(current, 1);
}
37
38
39
40
41
42
43
44
45
46
private String buildIdWithoutRestrictedWordsAndCelebrityNames(final Email last, final int count) {
    if (count == MAX_ATTEMPTS)
        throw new IllegalStateException("Exceeded the Max number of tries");
    String userName = findClosestNonRestrictiveWord(last.userName, RestrictedUserNames, 0);
    String domainName = findClosestNonRestrictiveWord(last.domainName, RestrictedDomainNames, 0);
    Email id = new Email(userName, domainName, dns);
    if (!id.isCelebrityName())
        return id.asString();
    return buildIdWithoutRestrictedWordsAndCelebrityNames(id.trimLastCharacter(), count + 1);
}

Influenced by Functional Programming, I’ve tried to use Tail recursion and Immutable objects here.

Also to get rid of massive duplication in code, I had to introduce a new Interface and 2 anonymous inner classes.

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface RestrictedWords {
    RestrictedWords RestrictedUserNames = new RestrictedWords() {
        @Override
        public boolean contains(final String word, final DomainNameService dns) {
            return dns.isRestrictedUserName(word);
        }
    };
 
    RestrictedWords RestrictedDomainNames = new RestrictedWords() {
        @Override
        public boolean contains(final String word, final DomainNameService dns) {
            return dns.isRestrictedDomainName(word);
        }
    };
 
    boolean contains(final String word, DomainNameService dns);
}

This should give you a decent idea of what the code does and how it does what it does. To check in detail, download the complete project source code.

Also I would recommend you check out some the comparison of code before and after.

Refactoring Teaser IV Solution Summary Report

Sunday, September 27th, 2009
Topic Before After
Project Size Production Code

  • Package =4
  • Classes =30
  • Methods = 90 (average 3/class)
  • LOC = 480 (average 5.33/method and 16/class)
  • Average Cyclomatic Complexity/Method = 1.89

Test Code

  • Package =3
  • Classes = 19
  • Methods = 106
  • LOC = 1379
Production Code

  • Package = 2
  • Classes =7
  • Methods = 24 (average 3.43/class)
  • LOC = 168 (average 6.42/method and 18.56/class)
  • Average Cyclomatic Complexity/Method = 1.83

Test Code

  • Package = 1
  • Classes = 4
  • Methods = 53
  • LOC =243
Code Coverage
  • Line Coverage: 88%
  • Block Coverage: 89%

Code Coverage Before

  • Line Coverage: 95%
  • Block Coverage: 94%

Code Coverage After

Cyclomatic Complexity Cylcomatic Complexity Before Cylcomatic Complexity After
Coding Convention Violation 85 0

Embracing Context Objects with Fluent Interfaces for my Tests

Thursday, September 24th, 2009

Of late I’ve been toying around with a new way of using Fluent Interfaces with a Context Object for my Tests. Esp. when I’m using Mockito.

In this post (Fluent Interfaces improve readability of my Tests), I’ve taken an example and demonstrated how I’ve evolved my tests to be more expressive. In my quest for getting my tests to communicate precisely to-the-point by hiding everything else which is noise, I’ve stared exploring another way of using Fluent Interfaces.

Following is an example:

1
2
3
4
5
6
7
8
9
@Test
public void redirectSubDomainsPermanently() {
    lets.assume("google.com").getsRedirectedTo("google.in").withSubDomain();
    response = domainForwardingServer.process(requestFor("blog.google.com"));
    lets.assertThat(response).contains(StatusCode.PermanentRedirect)
                             .location("google.in/blog").protocol("HTTP/1.1")
                             .connectionStatus("close").contentType("text/html")
                             .serverName("Directi Server 2.0");
}

lets and on are both Context objects which provide fluent, domain specific api to make the test very easy to read (communicative and expressive). It also helps me hide all my mocking/stubbing related code.

If you compare this with the original code, you can get a sense of what I’m talking about:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void redirectSubDomainsPermanently()  {
    when(request.hostName()).thenReturn("blog.google.com");
    when(request.protocol()).thenReturn("HTTP/1.1");
    when(request.path()).thenReturn("/");
    domain.setDomain("blog.google.com");
    domain.subDomainForwarding(true);
    domain.setForward("google.in");
    response = domainForwardingServer.processMessage(request);
    assertStatus(StatusCode.PermanentRedirect);
    assertLocation("google.in/blog");
    assertProtocol("HTTP/1.1");
    assertConnectionStatusIs("close");
    assertContentType("text/html");
    assertServerName("Directi Server 2.0");
}

Another example showing the Context object and Fluent Interface style is

1
2
3
4
5
6
@Test
public void avoidRestrictedWordsInIds() {
    lets.assume("naresh").isARestrictedUserName();
    List<String> suggestions = suggester.optionsFor(naresh_from_mumbai);
    lets.assertThat(suggestions).are("nares@jain.com", "nares@india.com", "nares@indian.com", "nares@mumbai.com");
}

As I said, I’m still toying around with this idea. If this works well, may be it will be part of some mocking framework soon.

Everything else is just Noise

Tuesday, September 22nd, 2009

Recently I was working on some code. The code was trying to tell me many things, but I was not sure if I was understanding what it was trying to communicate. It just felt irrelevant or noise at that moment. Somehow the right level of abstraction was missing.

When I started I had:

1
2
3
4
5
6
7
8
9
10
private final UserService userService = createMock(UserService.class);
private final DomainNameService dns = createMock(DomainNameService.class);
private final RandomNumberGenerator randomNumberGenerator = new RandomNumberGenerator() {
    @Override
    public long next() {
        return 9876543210L;
    }
};
private final IdentityGenerator identityGenerator = new IdentityGenerator(randomNumberGenerator, dns, userService);
private final User naresh_from_mumbai = new User("naresh", "jain", "mumbai", "india", "indian");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Test
public void avoidRestrictedWordsInIds() {
    expect(dns.isCelebrityName("naresh", "jain")).andStubReturn(false);
    expect(dns.validateFirstPartAndReturnRestrictedWordIfAny("naresh")).andStubReturn("naresh");
 
    expect(dns.isCelebrityName("nares", "jain")).andStubReturn(false);
    expect(dns.validateFirstPartAndReturnRestrictedWordIfAny("nares")).andStubReturn(null);
    expect(dns.validateSecondPartAndReturnRestrictedWordIfAny("jain")).andStubReturn(null);
    expect(userService.isIdentityAvailable("nares@jain.com")).andStubReturn(true);
 
    expect(dns.isCelebrityName("nares", "india")).andStubReturn(false);
    expect(dns.isCelebrityName("naresh", "india")).andStubReturn(false);
    expect(dns.validateFirstPartAndReturnRestrictedWordIfAny("nares")).andStubReturn(null);
    expect(dns.validateSecondPartAndReturnRestrictedWordIfAny("india")).andStubReturn(null);
    expect(userService.isIdentityAvailable("nares@india.com")).andStubReturn(true);
 
    expect(dns.isCelebrityName("nares", "indian")).andStubReturn(false);
    expect(dns.isCelebrityName("naresh", "indian")).andStubReturn(false);
    expect(dns.validateFirstPartAndReturnRestrictedWordIfAny("nares")).andStubReturn(null);
    expect(dns.validateSecondPartAndReturnRestrictedWordIfAny("indian")).andStubReturn(null);
    expect(userService.isIdentityAvailable("nares@indian.com")).andStubReturn(true);
 
    expect(dns.isCelebrityName("nares", "mumbai")).andStubReturn(false);
    expect(dns.isCelebrityName("naresh", "mumbai")).andStubReturn(false);
    expect(dns.validateFirstPartAndReturnRestrictedWordIfAny("nares")).andStubReturn(null);
    expect(dns.validateSecondPartAndReturnRestrictedWordIfAny("mumbai")).andStubReturn(null);
    expect(userService.isIdentityAvailable("nares@mumbai.com")).andStubReturn(true);
 
    replay(userService, dns);
 
    List<String> generatedIDs = identityGenerator.getGeneratedIDs(naresh_from_mumbai);
    List<String> expectedIds = ids("nares@jain.com", "nares@india.com", "nares@indian.com", "nares@mumbai.com");
 
    assertEquals(expectedIds, generatedIDs);
 
    verify(userService, dns);
}

As you can see, my first reaction after looking at this code was that there is too much going on, most of which is duplicate. So cleaned it up a bit and made it more expressive by

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private final Context lets = new Context(userService, dns);
 
@Test
public void avoidRestrictedWordsInIds() {
    lets.assume("naresh").plus("jain").isNotACelebrityName();
    lets.assume("naresh").isARestrictedUserName();
 
    lets.assume("nares").plus("jain").isNotACelebrityName();
    lets.assume("nares").isNotARestrictedUserName();
    lets.assume("jain").isNotARestrictedDomainName();
    lets.assume().identity("nares@jain.com").isAvailable();
 
    lets.assume("nares").plus("india").isNotACelebrityName();
    lets.assume("nares").isNotARestrictedUserName();
    lets.assume("india").isNotARestrictedDomainName();
    lets.assume().identity("nares@india.com").isAvailable();
 
    lets.assume("nares").plus("indian").isNotACelebrityName();
    lets.assume("nares").isNotARestrictedUserName();
    lets.assume("indian").isNotARestrictedDomainName();
    lets.assume().identity("nares@indian.com").isAvailable();
 
    lets.assume("nares").plus("mumbai").isNotACelebrityName();
    lets.assume("nares").isNotARestrictedUserName();
    lets.assume("mumbai").isNotARestrictedDomainName();
    lets.assume().identity("nares@mumbai.com").isAvailable();
 
    List<String> generatedIds = suggester.generateIdsFor(naresh_from_mumbai);
 
    lets.assertThat(generatedIds).are("nares@jain.com", "nares@india.com", "nares@indian.com", "nares@mumbai.com");
}

By introducing a new class called Context and moving all the mocking code into that, my test looked lot more clear. I was also able to create an abstraction that could communicate intent much more easily.

Next I reduced the clutter further by creating another level of abstraction as follows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void avoidRestrictedWordsInIds() {
    lets.assume("naresh", "jain").isNotACelebrityName();
    lets.assume("naresh").isARestrictedUserName();
 
    for (final String[] identityTokens : list(_("nares", "jain"), _("nares", "india"), _("nares", "indian"), _("nares", "mumbai"))) {
        lets.assume(identityTokens[0], identityTokens[1]).isNotACelebrityName();
        lets.assume(identityTokens[0]).isNotARestrictedUserName();
        lets.assume(identityTokens[1]).isNotARestrictedDomainName();
        lets.assume().identity(identityTokens[0] + "@" + identityTokens[1] + ".com").isAvailable();
    }
 
    List<String> generatedIds = suggester.generateIdsFor(naresh_from_mumbai);
 
    lets.assertThat(generatedIds).are("nares@jain.com", "nares@india.com", "nares@indian.com", "nares@mumbai.com");
}

But at this point, even though the code ended up being very dense, it was very difficult to understand what was going on and why so. In a desperate search for simplicity and better communication, I ended up with

1
2
3
4
5
6
@Test
public void avoidRestrictedWordsInIds() {
    lets.assume("naresh").isARestrictedUserName();
    List<String> generatedIds = suggester.suggestIdsFor(naresh_from_mumbai);
    lets.assertThat(generatedIds).are("nares@jain.com", "nares@india.com", "nares@indian.com", "nares@mumbai.com");
}

What is interesting about this is that I made some simple assumption saying:

  • every name is not a celebrity name unless specified
  • every user name is a valid (non-restricted) user name unless specified
  • every domain name is a valid (non-restricted) domain name unless specified
  • every identity is available unless specified

All these assumptions are now capture in my Context object and rest of my tests can happily focus on what really matters. I really liked the way this reduced the clutter in my tests without compromising on communication.

Setting up Huawei Mobile Connect on Snow Leopard

Monday, September 21st, 2009

Recently I upgraded to Snow Leopard and found my Huawei Modem (Model: EC168C) which came with Tata Indicom Photon+ does not work any more. On Huawei’s user forums they claim that these modems are not tested with Snow Leopard.

I tried to manually set up the modem in my network settings under System Preferences and it seems to work. Following are the steps I took to set up a new network service:

Add New Network Service

Once we click on the + button, select

Select Huawei Modem Interface

Once you select the Huawei Modem, you will need to fill the following info

Configure Modem Settings

Please fill in the exact same info as shown in the image above. Note that password is “internet”. Make sure the “Show modem status in menu bar” check box is checked. You’ll see later, we use this from the menu bar to connect to the internet.

Once you have entered the data as shown above, clicking on the Advanced… button will show:

Advanced Settings

Make sure to select “Sierra Wireless” as the Vendor (even though you are using Tata Indicom).

If you are using Photon Plus, then select “au by KDDI” as the Vendor. Sierra won’t work.

Click OK and then Apply.

Now you are all set to connect to the internet using your Photon account. Insert your USB modem, do not use the MobileConnect.app; instead use the Menu Bar to connect:

Connectin to the Internet from Menu Bar

Hopefully this explains the step by step set-up process to get our Huawei Modem to work on  Snow Leopard.

Why big Agile Conferences don’t have anything New?

Thursday, September 17th, 2009

At the Agile 2009 conference, Martin Fowler, Ron Jeffries, Chet Hendrickson, Mary Poppendieck and I had a very interesting discussion about “why some of us felt that there was nothing new at the Agile 2009 conference”. (or even if there were interesting topics, the signal to noise ratio was too small to find it).

Martin’s hypothesis (paraphrased):

Back in the late 90s and early 2000s, the core of the software development problem with regards to process was hashed out and most of the principles & techniques were flushed out via the Agile Manifesto and other techniques. (which of course was the easy bit). What is happening now is, most companies are trying to implement those ideas on their projects inside their organizations. Implementing those ideas is rather tricky and needs a lot of creative tweaking at project level. Its difficult to pull out any generic topics from these implementation and present it to a broad audience @ Agile 200x confs. Hence it feels like there is nothing new.

After which Martin asked Ron, if he has seen anything new on the mailing lists. Ron resonated with Martin. Everyone else seemed to agree.

Overall I’m convinced that this hypothesis makes sense. However I feel:

  • Even though implementing Agile techniques on projects needs lot of creative tweaking, we can still find patterns and meta-approaches to implementing/adopting agile. For Ex: Applying Theory of Constraints and Just-in-Time practices to coaching agile teams.
  • Personally I don’t find agile implementation @ large enterprises interesting. But I do see a lot of innovation happening in start-ups and small product companies. They are doing things which agilists might consider taboo. If we look at some of the Web 2.0 product companies, they are solving a lot of interesting problems like deploying to production multiple times a day, embracing fully distributed teams, etc.
  • Integrating UX and Operations team into the development team is still an open issue. Few companies have done some interesting work in this space.
  • And so on…

I feel majority of the Agile community has got into a “preaching mode” and very few people are actually building their own products (eating their own dog food.) This attitude attracts a certain kind of people to the conference and I’m quite skeptical to find innovative new ideas in this crowd. With so much noise its also very easy to miss some weak signals which have potential.

I do know a few people who are doing some really interesting stuff (they are turned off by the Agile brand and generally don’t hang around in these circles). Personally I want us, as a community, to be more inclusive of these people.

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.

    Licensed under
Creative Commons License