Skip to main content

Keep Changes Small: A Happy Jack Story

April 27, 2004

{cs.r.title}






Jack walked into the office one Monday morning. Underneath his arm, he carried a rolled-up piece of poster paper. He walked to the back of the building, where a group of couches and tables made up the "Engineering Conference Room," and used masking tape to stick his homemade poster to the wall. On it, Jack had carefully written in block letters, "KEEP CHANGES SMALL." Satisfied, he went to the cube the company let him use and waited for the developers to get there.

While he waited, Jack worked on a document for the development manager. Like most of Jack's documents, it was one page and covered a single idea. Jack described the current quality curve of the product. In the main branch of the code, where development on the next release was taking place, the curve looked like this:

Figure 1
Figure 1. Actual quality curve

There had been an initial batch of bug fixes left over from the current release that had been fixed, followed by new feature development -- and new bugs. Only now, near the release date, were those new bugs being addressed, and the curve starting to turn up again. The NextGen branch, where development on the "next plus one" release was happening, was even more chaotic.

Jack wanted to move towards a curve like this:

Figure 2
Figure 2. Ideal quality curve

If each change was small, well-contained, and easily tested, then the risk of any particular change could be minimized. So Jack proposed a new initiative, boldly titled: Keep Changes Small. This would be an effort to help the developers stay focused on making lots of small changes instead of complex big ones.

After a quick proof-read, Jack printed the sheet and placed it on the development manager's chair, and waited.

A few hours later, the document had generated the desired behavior. In response to an email from the development manager, the team was gathered in the Engineering Conference Room. Everyone looked expectantly at Jack, knowing he was probably the root cause of the meeting.

Jack smiled, and said, "I overheard a discussion Monday about the NextGen branch. Now, I don't know much about CVS, so forgive me if I get it wrong, but it sounds like we have two separate source trees right now. I thought about that for a little while and asked a few questions around the office. I want to share my observations about our present situation."

"Every time we change the code, we hope to make it better. However, there is a risk that we are making it worse. In my experience, bigger changes mean bigger risk. Bigger changes are also more complex, which means that it is hard to wrap your head around the whole set of changes, and harder to undo them if they turn out not to work."

"Take the NextGen branch, for example. Even if you make lots of little changes to that branch, that is still one whopper of a change when you merge it back in. So maybe we can figure out how to pull those changes in incrementally instead of in one big bang."

Jack pointed to his posterboard. "The reason I made this poster last night was to help you focus on the idea of keeping changes small, and see if that had a positive impact on the rest of your work. One way to think of it is, 'What can I do in the next five or ten minutes that will still compile and run, but will improve the system?' You don't have to make every change a five-minute change, but asking the question may help you see a direction to take."

Jack waited. Like many sharp people, each person on the team tended to be defensive about their areas of expertise. Since they were all good at a lot of different things, he knew he could count on at least one skeptic today.

Stu stepped up. "I understand your point and think it's fine and all. But that won't work with what I'm doing on NextGen. How are we supposed to make major architectural changes in tiny ten-minute chunks?"

Jack smiled, "I was hoping you would ask! Is there a particular part of NextGen that you're dealing with right now that we could work on together? Maybe you'll get some ideas to share."

Stu set aside some time that afternoon to work with Jack. The rest of the team decided to get together the next morning to hear about their adventure.

At 1:45, Jack was waiting when Stu got back from lunch. "Ready to get started? How about we begin with a quick sketch of the current system, to better understand what we're trying to do?"

Stu grabbed an index card and made a quick GML sketch:

Figure 3
Figure 3. Sketch of DataStore

"Basically, I'm rewriting the DataStore from the ground up. The old version uses flat files and doesn't have any transaction support. I'm switching it to use the database and be fully transactioned. Every part of the system touches the DataStore, so it's a big change. While I'm in there, I'm also cleaning up some really crufty hacks that should never have been written."

Jack thought for a minute. "So, I'm hearing that the DataStore is pretty tightly coupled to the rest of the system. How do other components call into it?"

"Let me show you," Stu said, navigating through his IDE.

...

DataStore ds = new DataStore();
ds.load("/usr/local/bigco/bigapp/conf.txt");

Hashtable props = ds.getProps();
String connPort = props.get("connPort");

...

props.set("connPort",userInput.get("connPort"));
ds.save();

...

"See, every time the system needs some of the static data, it has to make a call into the DataStore. This is also how we save configuration changes. If we're halfway through saving a set of changes and the system crashes, when it comes back up it will be in an inconsistent state. That's why we need transaction support."

Jack thought some more. "If we were to loosen the coupling between the DataStore and the rest of the system," he said, "would it be easier to change the DataStore's behavior?"

Stu nodded. "Sure."

"So. How do we make it more loosely coupled?" Jack reached for the sketch and added a new box. He continued, "One way to make that happen is to insert an abstraction layer." Jack labeled the new box FlexibleDataStore and drew a dotted line to isolate the DataStore.

Figure 4
Figure 4. Sketch of modified DataStore

"So let's start with a new class called FlexibleDataStore."

Stu moused around and typed in the new class name. "Now what?"

"Commit it," Jack said, "and put it on the main branch instead of NextGen."

"But it doesn't do anything!"

"That makes it very low risk." Jack smiled happily.

Stu clicked Commit, added a snarky log message, and said "Now what?"

"Now let's think about how people will use our FlexibleDataStore. Obviously, they want to get values out and put values in. So let's write those methods. For now, they can just use an old DataStore to do the real work."

Stu typed:

public class FlexibleDataStore {
    private DataStore ds = new DataStore();

    public String get(String s) {
        ds.load("/usr/local/bigco/bigapp/conf.txt");
        return ds.get(s);
    }

    public String set(String key, String value) {
        ds.load("/usr/local/bigco/bigapp/conf.txt");
        ds.set(key,value);
        ds.save();
    }
}

"Great. Let's commit those changes."

After doing so, Stu suggested, "Now we need to make the system use the new FlexibleDataStore instead of DataStore."

Jack nodded. "Exactly. You can commit after each change, because it is fairly low risk moving from DataStore to FlexibleDataStore. In fact, each time you do it, the risk gets lower, because you know all of the previous changes have worked." He paused, then added, "Of course, if you were writing unit tests throughout, those would also tell you if everything was still working."

Excitedly (and missing the hint about unit tests altogether), Stu started looking for classes to change. Then he stopped, "What about the transaction processing? We haven't added that yet."

"You're right," said Jack, "If you're going to change all the classes to use FlexibleDataStore, you might as well add the calls to handle transactions while you're there. You don't want to have to go back and edit all of those classes a second time. How can we minimize the risk and minimize the annoyance factor?"

Stu pondered. "Well," he finally ventured, "I guess we could add methods to start a transaction and then commit it. I can add those calls while I'm switching to FlexibleDataStore." He thought some more. "For now, those methods can just be no-ops and then I can flesh them out later."

"Good idea! You'll probably want to save implementing them for after you add the database support. Before I let you get started converting classes, do you have any thoughts on how to manage the risk while moving to the database?"

"That's a good question. I'd gotten so excited about what we'd done so far, I hadn't realized that we hadn't accomplished much towards our goal." Stu looked at Jack, but Jack was waiting for Stu to figure it out.

After a minute, Jack prompted him, "Remember the main rules for being able to commit: it compiles, it runs, and it doesn't make things worse -- and it passes all the tests, of course."

"I guess I could add a second type of DataStore for FlexibleDateStore to use. As long as it wasn't being used yet, I could add methods to it. Maybe even a runtime flag to say which DataStore to use, so I could test."

"Ah," said Jack, "Be careful. You've almost fallen into a trap. Do you see what it is?"

Stu pondered, and then smiled slowly. "If I add a bunch of code to the database-driven DataStore without hooking it up incrementally, then when I finally do hook it up, that's equivalent to one big change! All my risk came back!"

Jack beamed. "Exactly. So, what about adding a runtime flag to FlexibleDataStore so you can test against both back ends?"

"That would let me test all long," said Stu. "And, it would still protect the demo system if people load the latest code onto it."

Jack said, "It still might be a good idea to send around an email to let everyone know what you're up to, in case they're going to be touching nearby code. Sounds like you've got the idea. I'll let you work."

The next morning, the team reassembled. As soon as Stu walked in, Dan said, "Stu, you must have committed 100 times yesterday! I was flooded with email notices. Why didn't you just commit at the end of the day like usual?"

Stu explained. "Well, to begin with, by committing lots of little changes, it's easy to know what to roll back if something breaks. If it turns out one of the changes I made causes a problem, we can know pretty much which lines of code the bug is in."

"I also found it much easier to focus on what I was doing. I even wrote a few unit tests while I was going! Every change I made yesterday resulted in a clean build. Hopefully, each one made the system better, but at least none of them made things worse."

Dan grumbled a little, several people asked Stu to explain more about what he had worked on, and a very happy Jack smiled.

Author's Note: Jack used a very common software pattern to loosen the coupling in the system. Jack usually calls the pattern Facade, but it goes by many other names. Shield, Mediator, Bridge, Wrapper, and Driver are just a few. I always think of it as a wrapper, but I suspect Shield is the most general name. Of course, some people say it's not a pattern at all, but just good factoring.

Michael Ivey Michael Ivey is Senior Partner with Ivey & Brown, Inc., a consulting firm specializing in Scrum and Ruby on Rails.
Related Topics >> Education   |   Extreme Programming   |